| from __future__ import annotations |
|
|
| import os |
| import itertools |
| import json |
| from pathlib import Path |
|
|
| import gradio as gr |
| import gradio.component_meta as gradio_component_meta |
| from gradio.components.base import server |
|
|
| from shared.utils import files_locator as fl |
|
|
|
|
| VIDEO_FILE_EXTENSIONS = {".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v", ".mpeg", ".mpg", ".ogv"} |
| CHECKPOINT_FILE_EXTENSIONS = {".safetensors", ".safetensor", ".sft", ".ckpt", ".bin", ".binn", ".pt", ".pth", ".gguf", ".onnx"} |
| LAST_DIRECTORY_CONFIG_KEY = "local_file_picker_last_dir" |
| _server_config: dict | None = None |
| _tooltip_counter = itertools.count(1) |
|
|
|
|
| def configure_last_directory_store(server_config: dict | None) -> None: |
| global _server_config |
| _server_config = server_config |
|
|
|
|
| def _normalize_extensions(file_extensions: set[str] | list[str] | tuple[str, ...] | None) -> set[str]: |
| return {f".{str(ext).strip().lower().lstrip('.')}" for ext in (file_extensions or []) if str(ext).strip()} |
|
|
|
|
| def _available_roots() -> list[tuple[str, str]]: |
| roots: list[tuple[str, str]] = [] |
| if os.name == "nt": |
| try: |
| import ctypes |
|
|
| drive_mask = ctypes.windll.kernel32.GetLogicalDrives() |
| roots = [(f"{chr(ord('A') + index)}:\\", f"{chr(ord('A') + index)}:\\") for index in range(26) if drive_mask & (1 << index)] |
| except Exception: |
| roots = [] |
| else: |
| roots = [("/", "/")] |
| current_root = str(Path.cwd().anchor or "").strip() |
| if current_root and current_root.casefold() not in {value.casefold() for _label, value in roots}: |
| roots.insert(0, (current_root, current_root)) |
| return roots or [(".", ".")] |
|
|
|
|
| def _is_under_root(path: str, root: str) -> bool: |
| path = os.path.abspath(os.path.normpath(path)) |
| root = os.path.abspath(os.path.normpath(root)) |
| try: |
| return os.path.commonpath([path, root]).casefold() == root.casefold() |
| except ValueError: |
| return False |
|
|
|
|
| def _is_probable_url(value: str) -> bool: |
| return "://" in str(value) or str(value).startswith(("mailto:", "urn:")) |
|
|
|
|
| def _is_non_local_entry(value: str) -> bool: |
| return _is_probable_url(value) or str(value).startswith("=") |
|
|
|
|
| _create_or_modify_pyi = gradio_component_meta.create_or_modify_pyi |
| gradio_component_meta.create_or_modify_pyi = lambda *args, **kwargs: None |
|
|
|
|
| class _FilteredFileExplorer(gr.FileExplorer): |
| def __init__( |
| self, |
| glob: str = "**/*", |
| *, |
| value=None, |
| file_count: str = "multiple", |
| root_dir: str | Path = ".", |
| ignore_glob: str | None = None, |
| label: str | None = None, |
| every=None, |
| inputs=None, |
| show_label: bool | None = None, |
| container: bool = True, |
| scale: int | None = None, |
| min_width: int = 160, |
| height: int | str | None = None, |
| max_height: int | str | None = 500, |
| min_height: int | str | None = None, |
| interactive: bool | None = None, |
| visible: bool = True, |
| elem_id: str | None = None, |
| elem_classes: list[str] | str | None = None, |
| render: bool = True, |
| key: int | str | None = None, |
| file_extensions: set[str] | list[str] | tuple[str, ...] | None = None, |
| ) -> None: |
| self.file_extensions = _normalize_extensions(file_extensions) |
| super().__init__(glob=glob, value=value, file_count=file_count, root_dir=root_dir, ignore_glob=ignore_glob, label=label, every=every, inputs=inputs, show_label=show_label, container=container, scale=scale, min_width=min_width, height=height, max_height=max_height, min_height=min_height, interactive=interactive, visible=visible, elem_id=elem_id, elem_classes=elem_classes, render=render, key=key) |
|
|
| @classmethod |
| def get_component_class_id(cls) -> str: |
| return gr.FileExplorer.get_component_class_id() |
|
|
| def get_block_name(self) -> str: |
| return "fileexplorer" |
|
|
| def get_block_class(self) -> str: |
| return "fileexplorer" |
|
|
| def get_config(self): |
| config = super().get_config() |
| config.pop("file_extensions", None) |
| return config |
|
|
| def _strip_root(self, path: str) -> str: |
| root_dir = os.path.abspath(os.path.normpath(self.root_dir)) |
| normalized_path = os.path.abspath(os.path.normpath(path)) |
| try: |
| relative_path = os.path.relpath(normalized_path, root_dir) |
| except ValueError: |
| return normalized_path |
| if relative_path == "." or relative_path.startswith(f"..{os.path.sep}") or relative_path == "..": |
| return normalized_path |
| return relative_path |
|
|
| @server |
| def ls(self, subdirectory=None): |
| if subdirectory is None: |
| subdirectory = [] |
| try: |
| full_subdir_path = self._safe_join(subdirectory) |
| subdir_items = sorted(os.listdir(full_subdir_path), key=str.casefold) |
| except OSError: |
| return [] |
| files, folders = [], [] |
| for item in subdir_items: |
| full_path = os.path.join(full_subdir_path, item) |
| if os.path.isfile(full_path): |
| if not self.file_extensions or Path(full_path).suffix.lower() in self.file_extensions: |
| files.append({"name": item, "type": "file", "valid": True}) |
| else: |
| folders.append({"name": item, "type": "folder", "valid": False}) |
| return folders + files |
|
|
|
|
| gradio_component_meta.create_or_modify_pyi = _create_or_modify_pyi |
|
|
|
|
| class LocalFilePickerTextbox: |
| def __init__( |
| self, |
| *, |
| label: str, |
| value: str = "", |
| file_extensions: set[str] | list[str] | tuple[str, ...] | None = None, |
| multiselect: bool = False, |
| popup_title: str = "Browse Local Files", |
| browse_label: str = "\U0001F4C2", |
| browse_tooltip: str | None = None, |
| lines: int | None = None, |
| textbox_scale: int = 8, |
| button_min_width: int = 34, |
| browser_height: int = 320, |
| ) -> None: |
| self.label = label |
| self.value = value |
| self.file_extensions = _normalize_extensions(file_extensions) |
| self.multiselect = bool(multiselect) |
| self.popup_title = popup_title |
| self.browse_label = browse_label |
| self.browse_tooltip = browse_tooltip or ("Browse local files" if self.multiselect else "Browse local file") |
| self.tooltip_class = f"wangp-local-file-picker-tooltip-{next(_tooltip_counter)}" |
| self.lines = lines if lines is not None else (4 if self.multiselect else 1) |
| self.textbox_scale = textbox_scale |
| self.button_min_width = button_min_width |
| self.browser_height = browser_height |
| self.roots = _available_roots() |
| paths = self._parse_text(value) |
| self.default_dir = self._start_dir_for_paths(paths) or self._remembered_dir() or self.roots[0][1] |
| self.default_root = self._root_for_paths(paths) or self._root_for_directory(self.default_dir) or self.roots[0][1] |
| self.textbox: gr.Textbox | None = None |
| self.browse_button: gr.Button | None = None |
| self.popup: gr.Column | None = None |
| self.drive: gr.Dropdown | None = None |
| self.current_dir: gr.Textbox | None = None |
| self.up_button: gr.Button | None = None |
| self.close_button: gr.Button | None = None |
| self.exit_button: gr.Button | None = None |
| self.browser: gr.FileExplorer | None = None |
|
|
| def mount(self) -> gr.Textbox: |
| gr.HTML(f"<style>{self.get_css()}{self._tooltip_css()}</style>", elem_classes=["wangp-local-file-picker-style"]) |
| with gr.Column(elem_classes=["wangp-local-file-picker-field", self.tooltip_class]): |
| self.textbox = gr.Textbox(label=self.label, value=self.value, lines=self.lines, scale=self.textbox_scale) |
| self.browse_button = gr.Button(self.browse_label, min_width=1, elem_classes=["wangp-local-file-picker-browse-btn"]) |
| self._mount_popup() |
| self._wire_events() |
| return self.textbox |
|
|
| def _tooltip_css(self) -> str: |
| return f""" |
| .{self.tooltip_class} .wangp-local-file-picker-browse-btn::after {{ |
| content: {json.dumps(self.browse_tooltip)}; |
| }} |
| """ |
|
|
| def _mount_popup(self) -> None: |
| with gr.Column(visible=False, elem_classes=["wangp-local-file-picker-popup"]) as self.popup: |
| with gr.Column(elem_classes=["wangp-model-info-card", "wangp-local-file-picker-card"]): |
| with gr.Row(elem_classes=["wangp-model-info-titlebar", "wangp-local-file-picker-titlebar"]): |
| gr.HTML(f"<div class='wangp-model-info-heading'>{self.popup_title}</div>", elem_classes=["wangp-local-file-picker-heading"]) |
| self.close_button = gr.Button("x", elem_classes=["wangp-model-info-close", "wangp-local-file-picker-close"], min_width=26, scale=0) |
| with gr.Column(elem_classes=["wangp-model-info-content", "wangp-local-file-picker-content"]): |
| with gr.Row(elem_classes=["wangp-local-file-picker-location-row"]): |
| self.drive = gr.Dropdown(self.roots, value=self.default_root, show_label=False, container=False, scale=1, min_width=90, elem_classes=["wangp-local-file-picker-drive"]) |
| self.current_dir = gr.Text(show_label=False, value=self._display_dir(self.default_root, self.default_dir), placeholder="Folder", interactive=True, container=False, scale=8, min_width=240, elem_classes=["wangp-local-file-picker-folder-input"]) |
| self.up_button = gr.Button("Up", min_width=58, elem_classes=["wangp-local-file-picker-up-btn"]) |
| self.browser = _FilteredFileExplorer(value=self._browser_value(self.default_dir, self._parse_text(self.value)), file_extensions=self.file_extensions, glob="**/*.*", show_label=False, root_dir=self.default_dir, file_count="multiple" if self.multiselect else "single", height=self.browser_height, interactive=True) |
| self.exit_button = gr.Button("Exit", visible=self.multiselect, elem_classes=["wangp-local-file-picker-exit-btn"], min_width=72) |
|
|
| def _wire_events(self) -> None: |
| assert self.textbox is not None |
| assert self.browse_button is not None |
| assert self.popup is not None |
| assert self.drive is not None |
| assert self.current_dir is not None |
| assert self.up_button is not None |
| assert self.close_button is not None |
| assert self.exit_button is not None |
| assert self.browser is not None |
|
|
| self.browse_button.click(fn=self._open_popup, inputs=[self.textbox], outputs=[self.popup, self.drive, self.current_dir, self.browser], queue=False, show_progress="hidden") |
| self.drive.change(fn=self._change_drive, inputs=[self.drive, self.textbox], outputs=[self.current_dir, self.browser], queue=False, show_progress="hidden") |
| self.current_dir.blur(fn=self._change_folder, inputs=[self.drive, self.current_dir, self.textbox], outputs=[self.drive, self.current_dir, self.browser], queue=False, show_progress="hidden") |
| self.current_dir.submit(fn=self._change_folder, inputs=[self.drive, self.current_dir, self.textbox], outputs=[self.drive, self.current_dir, self.browser], queue=False, show_progress="hidden") |
| self.up_button.click(fn=self._go_up, inputs=[self.drive, self.current_dir, self.textbox], outputs=[self.drive, self.current_dir, self.browser], queue=False, show_progress="hidden") |
| self.close_button.click(fn=lambda: gr.update(visible=False), outputs=[self.popup], queue=False, show_progress="hidden") |
| self.exit_button.click(fn=lambda: gr.update(visible=False), outputs=[self.popup], queue=False, show_progress="hidden") |
| self.browser.change(fn=self._select_files, inputs=[self.browser, self.textbox, self.drive, self.current_dir], outputs=[self.textbox, self.popup], queue=False, show_progress="hidden") |
|
|
| def _open_popup(self, current_text: str): |
| paths = self._parse_text(current_text) |
| current_dir = self._start_dir_for_paths(paths) or self._remembered_dir() or self.default_dir |
| selected_root = self._root_for_directory(current_dir) or self.default_root |
| self._remember_dir(current_dir) |
| return gr.update(visible=True), gr.update(value=selected_root), gr.update(value=self._display_dir(selected_root, current_dir)), self._browser_update(current_dir, paths) |
|
|
| def _change_drive(self, selected_root: str, current_text: str): |
| current_dir = str(selected_root or self.default_root) |
| self._remember_dir(current_dir) |
| return gr.update(value=""), self._browser_update(current_dir, self._parse_text(current_text)) |
|
|
| def _change_folder(self, selected_root: str, displayed_dir: str, current_text: str): |
| current_dir = self._resolve_dir_for_root(selected_root, displayed_dir) |
| if not os.path.isdir(current_dir): |
| return gr.skip(), gr.skip(), gr.skip() |
| selected_root = self._root_for_directory(current_dir) or selected_root or self.default_root |
| self._remember_dir(current_dir) |
| return gr.update(value=selected_root), gr.update(value=self._display_dir(selected_root, current_dir)), self._browser_update(current_dir, self._parse_text(current_text)) |
|
|
| def _go_up(self, selected_root: str, displayed_dir: str, current_text: str): |
| current_dir = self._resolve_dir_for_root(selected_root, displayed_dir) |
| current_root = self._root_for_directory(current_dir) |
| if current_root and os.path.abspath(os.path.normpath(current_root)).casefold() == current_dir.casefold(): |
| parent_dir = current_root |
| else: |
| parent_dir = os.path.dirname(current_dir) or current_root or current_dir |
| selected_root = self._root_for_directory(parent_dir) or self.default_root |
| self._remember_dir(parent_dir) |
| return gr.update(value=selected_root), gr.update(value=self._display_dir(selected_root, parent_dir)), self._browser_update(parent_dir, self._parse_text(current_text)) |
|
|
| def _select_files(self, selected_paths, current_text: str, selected_root: str, displayed_dir: str): |
| current_dir = self._resolve_dir_for_root(selected_root, displayed_dir) |
| selected = self._coerce_selected_paths(selected_paths) |
| if self.multiselect: |
| current_paths = self._current_entries_outside_dir(current_text, current_dir) |
| current_paths.extend(selected) |
| return self._format_paths(current_paths), gr.update() |
| selected_text = self._format_paths(selected) |
| if not selected_text: |
| return gr.skip(), gr.update(visible=True) |
| current_text = self._format_paths(self._parse_text(current_text)) |
| return selected_text, gr.update(visible=selected_text == current_text and bool(selected_text)) |
|
|
| def _browser_update(self, current_dir: str, paths: list[str]): |
| self._remember_dir(current_dir) |
| return gr.update(root_dir=current_dir, value=self._browser_value(current_dir, paths)) |
|
|
| def _browser_value(self, root: str, paths: list[str]): |
| root_paths = [path for path in paths if _is_under_root(path, root)] |
| if self.multiselect: |
| return root_paths |
| return root_paths[0] if root_paths else [] |
|
|
| def _root_for_paths(self, paths: list[str]) -> str | None: |
| for path in paths: |
| if not self._path_is_allowed(path): |
| continue |
| for _label, root in self.roots: |
| if _is_under_root(path, root): |
| return root |
| return None |
|
|
| def _root_for_directory(self, directory: str) -> str | None: |
| for _label, root in self.roots: |
| if _is_under_root(directory, root): |
| return root |
| return None |
|
|
| def _resolve_dir_for_root(self, root: str | None, displayed_dir: str | None) -> str: |
| root = str(root or self.default_root) |
| displayed_dir = str(displayed_dir or "").strip().strip('"') |
| if os.path.isabs(displayed_dir): |
| return os.path.abspath(os.path.normpath(displayed_dir)) |
| return os.path.abspath(os.path.normpath(os.path.join(root, displayed_dir))) if displayed_dir else os.path.abspath(os.path.normpath(root)) |
|
|
| def _display_dir(self, root: str, directory: str) -> str: |
| root = os.path.abspath(os.path.normpath(root or self.default_root)) |
| directory = os.path.abspath(os.path.normpath(directory or root)) |
| try: |
| relative = os.path.relpath(directory, root) |
| except ValueError: |
| return directory |
| return "" if relative == "." else relative |
|
|
| def _start_dir_for_paths(self, paths: list[str]) -> str | None: |
| for path in paths: |
| if self._path_is_allowed(path): |
| return os.path.dirname(path) |
| return None |
|
|
| def _remembered_dir(self) -> str | None: |
| value = str((_server_config or {}).get(LAST_DIRECTORY_CONFIG_KEY, "") or "").strip() |
| return os.path.abspath(os.path.normpath(value)) if value and os.path.isdir(value) else None |
|
|
| def _remember_dir(self, directory: str) -> None: |
| if _server_config is not None and directory and os.path.isdir(directory): |
| _server_config[LAST_DIRECTORY_CONFIG_KEY] = os.path.abspath(os.path.normpath(directory)) |
|
|
| def _parse_text(self, value: str | None) -> list[str]: |
| chunks = str(value or "").splitlines() if self.multiselect else [str(value or "")] |
| paths = [] |
| seen = set() |
| for chunk in chunks: |
| path = chunk.strip().strip('"') |
| if not path: |
| continue |
| if _is_non_local_entry(path): |
| continue |
| path = os.path.abspath(os.path.normpath(fl.uncompress_path(path))) |
| key = path.casefold() |
| if key not in seen: |
| paths.append(path) |
| seen.add(key) |
| return paths |
|
|
| def _coerce_selected_paths(self, selected_paths) -> list[str]: |
| if selected_paths is None: |
| return [] |
| paths = selected_paths if isinstance(selected_paths, list) else [selected_paths] |
| return [path for path in self._parse_text("\n".join(str(path) for path in paths)) if self._path_is_allowed(path)] |
|
|
| def _format_paths(self, paths: list[str]) -> str: |
| if self.multiselect: |
| return "\n".join(fl.compress_path(path) for path in paths) |
| return fl.compress_path(paths[0]) if paths else "" |
|
|
| def _current_entries_outside_dir(self, current_text: str, current_dir: str) -> list[str]: |
| entries = [] |
| seen = set() |
| for chunk in str(current_text or "").splitlines(): |
| entry = chunk.strip().strip('"') |
| if not entry: |
| continue |
| if _is_non_local_entry(entry): |
| normalized = entry |
| else: |
| path = os.path.abspath(os.path.normpath(fl.uncompress_path(entry))) |
| if _is_under_root(path, current_dir): |
| continue |
| normalized = path |
| key = normalized.casefold() |
| if key not in seen: |
| seen.add(key) |
| entries.append(normalized) |
| return entries |
|
|
| def _path_is_allowed(self, path: str) -> bool: |
| if not os.path.isfile(path): |
| return False |
| return not self.file_extensions or Path(path).suffix.lower() in self.file_extensions |
|
|
| @staticmethod |
| def get_css() -> str: |
| return """ |
| .wangp-local-file-picker-style { |
| display: none !important; |
| } |
| .wangp-local-file-picker-field { |
| position: relative !important; |
| } |
| .wangp-local-file-picker-field > .form { |
| border: 0 !important; |
| padding: 0 !important; |
| background: transparent !important; |
| box-shadow: none !important; |
| overflow: visible !important; |
| } |
| .wangp-local-file-picker-browse-btn { |
| position: absolute !important; |
| top: 0 !important; |
| right: 4px !important; |
| z-index: 3 !important; |
| width: 24px !important; |
| min-width: 24px !important; |
| max-width: 24px !important; |
| height: 24px !important; |
| min-height: 24px !important; |
| max-height: 24px !important; |
| padding: 0 !important; |
| flex: 0 0 24px !important; |
| font-size: 14px !important; |
| line-height: 1 !important; |
| border: 0 !important; |
| outline: 0 !important; |
| background: transparent !important; |
| box-shadow: none !important; |
| } |
| .wangp-local-file-picker-browse-btn > .form { |
| border: 0 !important; |
| outline: 0 !important; |
| padding: 0 !important; |
| background: transparent !important; |
| box-shadow: none !important; |
| overflow: visible !important; |
| } |
| .wangp-local-file-picker-browse-btn button, |
| .wangp-local-file-picker-browse-btn > button, |
| .wangp-local-file-picker-browse-btn button:hover, |
| .wangp-local-file-picker-browse-btn > button:hover, |
| .wangp-local-file-picker-browse-btn button:focus, |
| .wangp-local-file-picker-browse-btn > button:focus { |
| width: 24px !important; |
| min-width: 24px !important; |
| max-width: 24px !important; |
| height: 24px !important; |
| min-height: 24px !important; |
| max-height: 24px !important; |
| padding: 0 !important; |
| border: 0 !important; |
| outline: 0 !important; |
| border-radius: 0 !important; |
| background: transparent !important; |
| box-shadow: none !important; |
| color: var(--body-text-color, #172b3a) !important; |
| font-size: 14px !important; |
| line-height: 1 !important; |
| } |
| .wangp-local-file-picker-browse-btn *, |
| .wangp-local-file-picker-browse-btn *::before, |
| .wangp-local-file-picker-browse-btn *::after { |
| border-color: transparent !important; |
| outline: 0 !important; |
| box-shadow: none !important; |
| } |
| .wangp-local-file-picker-browse-btn::after { |
| position: absolute; |
| top: 28px; |
| right: 0; |
| width: max-content; |
| max-width: 260px; |
| padding: 5px 7px; |
| border: 1px solid var(--border-color-primary, rgba(17, 84, 118, 0.18)); |
| border-radius: 5px; |
| background: var(--background-fill-primary, #ffffff); |
| color: var(--body-text-color, #172b3a); |
| box-shadow: var(--shadow-drop, 0 4px 12px rgba(0, 0, 0, 0.14)); |
| font-size: 12px; |
| line-height: 1.25; |
| pointer-events: none; |
| opacity: 0; |
| visibility: hidden; |
| transition: opacity 0.12s ease, visibility 0s linear 0.12s; |
| } |
| .wangp-local-file-picker-browse-btn:hover::after { |
| opacity: 1; |
| visibility: visible; |
| transition-delay: 0.5s; |
| } |
| .wangp-local-file-picker-popup { |
| position: fixed !important; |
| top: 96px !important; |
| right: 32px !important; |
| width: min(780px, calc(100vw - 34px)) !important; |
| max-height: min(80vh, 760px) !important; |
| z-index: 1210 !important; |
| pointer-events: none !important; |
| } |
| .wangp-local-file-picker-popup > .form { |
| border: 0 !important; |
| padding: 0 !important; |
| background: transparent !important; |
| box-shadow: none !important; |
| } |
| .wangp-local-file-picker-card { |
| pointer-events: auto !important; |
| position: relative !important; |
| overflow: hidden !important; |
| border: 1px solid var(--border-color-primary, rgba(17, 84, 118, 0.16)) !important; |
| border-radius: 18px !important; |
| background: var(--background-fill-primary, rgba(255, 255, 255, 0.99)) !important; |
| box-shadow: 0 28px 62px rgba(7, 31, 48, 0.24) !important; |
| } |
| .wangp-local-file-picker-titlebar { |
| margin: 0 !important; |
| } |
| .wangp-local-file-picker-titlebar > .form { |
| border: 0 !important; |
| padding: 0 !important; |
| background: transparent !important; |
| } |
| .wangp-local-file-picker-heading, |
| .wangp-local-file-picker-heading > .html-container { |
| min-height: 0 !important; |
| padding: 0 !important; |
| border: 0 !important; |
| background: transparent !important; |
| } |
| .wangp-local-file-picker-titlebar .wangp-local-file-picker-close { |
| flex: 0 0 26px !important; |
| } |
| .wangp-local-file-picker-titlebar .wangp-local-file-picker-close button, |
| .wangp-local-file-picker-close { |
| width: 26px !important; |
| height: 26px !important; |
| min-width: 26px !important; |
| min-height: 26px !important; |
| padding: 0 !important; |
| } |
| .wangp-local-file-picker-content { |
| max-height: calc(min(80vh, 760px) - 46px) !important; |
| overflow: hidden !important; |
| padding-bottom: 14px !important; |
| } |
| .wangp-local-file-picker-content .file-wrap { |
| max-height: none !important; |
| } |
| .wangp-local-file-picker-location-row { |
| align-items: flex-end !important; |
| gap: 8px !important; |
| } |
| .wangp-local-file-picker-location-row .form { |
| border: 0 !important; |
| background: transparent !important; |
| } |
| .wangp-local-file-picker-drive { |
| min-width: 90px !important; |
| } |
| .wangp-local-file-picker-folder-input { |
| border: 1px solid var(--border-color-primary) !important; |
| border-radius: 6px !important; |
| background: var(--input-background-fill, var(--body-background-fill)) !important; |
| display: block !important; |
| min-width: 240px !important; |
| } |
| .wangp-local-file-picker-folder-input textarea, |
| .wangp-local-file-picker-folder-input input { |
| border: 1px solid var(--border-color-primary) !important; |
| border-radius: 6px !important; |
| background: var(--input-background-fill, var(--body-background-fill)) !important; |
| display: block !important; |
| width: 100% !important; |
| min-height: 32px !important; |
| height: 32px !important; |
| padding: 4px 8px !important; |
| } |
| .wangp-local-file-picker-content .file-wrap, |
| .wangp-local-file-picker-content .file-explorer, |
| .wangp-local-file-picker-content [data-testid="file-explorer"] { |
| overflow-y: auto !important; |
| } |
| .wangp-local-file-picker-exit-btn { |
| align-self: flex-end !important; |
| margin-top: 8px !important; |
| width: 72px !important; |
| min-width: 72px !important; |
| } |
| """ |
|
|