diff --git a/local_folder_picker.py b/local_folder_picker.py new file mode 100644 index 0000000..3622460 --- /dev/null +++ b/local_folder_picker.py @@ -0,0 +1,92 @@ +import platform +from pathlib import Path +from typing import Optional + +from nicegui import events, ui + + +class local_folder_picker(ui.dialog): + + def __init__(self, directory: str, *, + upper_limit: Optional[str] = ..., multiple: bool = False, show_hidden_files: bool = False) -> None: + """Local File Picker + + This is a simple file picker that allows you to select a file from the local filesystem where NiceGUI is running. + + :param directory: The directory to start in. + :param upper_limit: The directory to stop at (None: no limit, default: same as the starting directory). + :param multiple: Whether to allow multiple files to be selected. + :param show_hidden_files: Whether to show hidden files. + """ + super().__init__() + + self.path = Path(directory).expanduser() + if upper_limit is None: + self.upper_limit = None + else: + self.upper_limit = Path(directory if upper_limit == ... else upper_limit).expanduser() + self.show_hidden_files = show_hidden_files + + with self, ui.card(): + self.add_drives_toggle() + self.grid = ui.aggrid({ + 'columnDefs': [{'field': 'name', 'headerName': 'File'}], + 'rowSelection': 'multiple' if multiple else 'single', + }, html_columns=[0]).classes('w-96').on('cellDoubleClicked', self.handle_double_click) + with ui.row().classes('w-full justify-end'): + ui.button('Cancel', on_click=self.close).props('outline') + ui.button('Ok', on_click=self._handle_ok) + self.update_grid() + + def add_drives_toggle(self): + if platform.system() == 'Windows': + import win32api + drives = win32api.GetLogicalDriveStrings().split('\000')[:-1] + self.drives_toggle = ui.toggle(drives, value=drives[0], on_change=self.update_drive) + + def update_drive(self): + self.path = Path(self.drives_toggle.value).expanduser() + self.update_grid() + + def update_grid(self) -> None: + paths = list(self.path.glob('*')) + if not self.show_hidden_files: + paths = [p for p in paths if not p.name.startswith('.')] + paths.sort(key=lambda p: p.name.lower()) + paths.sort(key=lambda p: not p.is_dir()) + + self.grid.options['rowData'] = [ + { + 'name': f'📁 {p.name}' if p.is_dir() else p.name, + 'path': str(p), + } + for p in paths + ] + if (self.upper_limit is None and self.path != self.path.parent) or \ + (self.upper_limit is not None and self.path != self.upper_limit): + self.grid.options['rowData'].insert(0, { + 'name': '📁 ..', + 'path': str(self.path.parent), + }) + self.grid.update() + + def handle_double_click(self, e: events.GenericEventArguments) -> None: + self.path = Path(e.args['data']['path']) + if self.path.is_dir(): + self.update_grid() + else: + self.submit(self.path.parent) + + async def _handle_ok(self): + rows = await self.grid.get_selected_rows() + + if not rows: + self.submit(self.path) + + else: + p = Path(rows[0]['path']) + + if p.is_dir(): + self.submit(p) + else: + self.submit(p.parent) \ No newline at end of file diff --git a/nice.py b/nice.py index 006a1bd..b74732c 100644 --- a/nice.py +++ b/nice.py @@ -30,6 +30,7 @@ import watchfiles from functools import partial +from local_folder_picker import local_folder_picker from typing import Optional @@ -60,15 +61,14 @@ from starlette.middleware.base import BaseHTTPMiddleware SOCKET_PATH = 'tcp://localhost:9976' -EXPORT_PATH = Path("./test/export") -PROCESS_PATH = Path("./test/to_process") -ANON_PATH = Path("./test/anon") +EXPORT_PATH = Path("C:/uploader/export") +PROCESS_PATH = Path("C:/uploader/to_process") +ANON_PATH = Path("C:/uploader/anon") EXPORT_PATH.mkdir(exist_ok=True, parents=True) PROCESS_PATH.mkdir(exist_ok=True, parents=True) EXPORT_PATH.mkdir(exist_ok=True, parents=True) -BASE_SITE_URL = "http://localhost:8000" BASE_SITE_URL = "https://www.penracourses.org.uk" LOGIN_URL = f"{BASE_SITE_URL}/accounts/login/" @@ -82,6 +82,15 @@ REFRESH_TRIGGERED = False DELETE_FILES_ON_UPLOAD = False +DEBUG = False + + +if DEBUG: + BASE_SITE_URL = "http://localhost:8000" + EXPORT_PATH = Path("./test/export") + PROCESS_PATH = Path("./test/to_process") + ANON_PATH = Path("./test/anon") + rqst = None @@ -196,10 +205,11 @@ async def upload_files_start(progress, upload_queue): loaded_files_ui.refresh() uploaded_files_ui.refresh() -async def load_files_start(progress_bar, queue): +async def load_files_start(progress_bar, queue, custom_path=None): global loaded_files, loaded_series, loaded_series_data progress_bar.visible = True - new_loaded_files, new_loaded_series, new_loaded_series_data = await run.cpu_bound(load_files, queue) + + new_loaded_files, new_loaded_series, new_loaded_series_data = await run.cpu_bound(load_files, queue, custom_path) loaded_files.update(new_loaded_files) loaded_series.update(new_loaded_series) @@ -268,12 +278,13 @@ def uploaded_files_ui() -> None: for file, data in uploaded_files.items(): ui.item(f"{file} - {data}") -def load_files(q: Queue): +def load_files(q: Queue, src_path: Path | None = None): loaded_files: dict = {} loaded_series = defaultdict(list) loaded_series_data = {} # Move files - src_path = EXPORT_PATH + if src_path is None: + src_path = EXPORT_PATH to_process = [] for file in src_path.iterdir(): @@ -581,9 +592,19 @@ def main_page(): progressbar = ui.linear_progress(value=0).props('instant-feedback') anon_progress.visible = False + async def load_files_from_folder() -> None: + folder = await local_folder_picker("~") + logger.error(folder) + if folder is not None: + ui.notify(f"Selected folder: {folder}") + await load_files_start(anon_progress, queue, folder) + #return dir + else: + ui.notify("No folder selected", color='negative') + with ui.expansion('Extra', icon='build').classes('w-full'): ui.button("Load files", on_click=partial(load_files_start, anon_progress, queue)) - ui.button("Load files from folder", on_click=partial(load_files_start, anon_progress, queue)) + ui.button("Load files from folder", on_click=load_files_from_folder) ui.button("Reload anon", on_click=partial(reload_anonymized_start, anon_progress, queue)) with ui.expansion('File status', icon='view_list').classes('w-full'):