|
11 | 11 | import sys |
12 | 12 | from pathlib import Path |
13 | 13 | import streamlit as st |
14 | | -import streamlit.components.v1 as components |
15 | 14 |
|
16 | 15 | CURRENT_FILE = Path(__file__).resolve() |
17 | 16 | CODE_DIR = CURRENT_FILE.parents[1] |
18 | 17 | sys.path.append(str(CODE_DIR)) |
19 | 18 |
|
20 | | -from core.tree_builder import list_directory # noqa: E402 |
| 19 | +from core.tree_builder import list_directory, search_directory # noqa: E402 |
21 | 20 | from core.previewer import ( # noqa: E402 |
22 | 21 | get_file_type, |
23 | 22 | read_text_file, |
24 | 23 | read_table_file, |
25 | 24 | render_notebook_html, |
26 | 25 | render_pdf_pages, |
27 | | - read_docx_file, |
28 | | - read_pptx_file, |
| 26 | + render_docx_as_html, |
| 27 | + render_pptx_as_html, |
| 28 | + extract_pdf_links, |
29 | 29 | ) |
30 | 30 |
|
31 | 31 | st.set_page_config( |
|
208 | 208 | background: #94a3b8; |
209 | 209 | } |
210 | 210 |
|
| 211 | + /* Input labels */ |
| 212 | + .stTextInput label, |
| 213 | + div[data-testid="stTextInputRootElement"] label { |
| 214 | + color: #374151 !important; |
| 215 | + font-size: 0.85rem !important; |
| 216 | + } |
| 217 | +
|
211 | 218 | </style> |
212 | 219 | """, |
213 | 220 | unsafe_allow_html=True, |
214 | 221 | ) |
215 | 222 |
|
| 223 | +st.markdown( |
| 224 | + f""" |
| 225 | + <div style=" |
| 226 | + margin-bottom: 0.6rem; |
| 227 | + padding-left: 0.1rem; |
| 228 | + "> |
| 229 | + <h1 style=" |
| 230 | + margin-bottom:0rem; |
| 231 | + "> |
| 232 | + 🧭 FSS Navigator |
| 233 | + </h1> |
| 234 | + </div> |
| 235 | + """, |
| 236 | + unsafe_allow_html=True, |
| 237 | +) |
| 238 | + |
| 239 | +default_project_path = Path.cwd().resolve() |
| 240 | + |
| 241 | +if "project_path" not in st.session_state: |
| 242 | + st.session_state.project_path = str(default_project_path) |
| 243 | + |
| 244 | +project_path_input = st.text_input( |
| 245 | + "Project directory to inspect", |
| 246 | + value=st.session_state.project_path, |
| 247 | + placeholder="/path/to/project", |
| 248 | + label_visibility="collapsed", |
| 249 | +) |
| 250 | + |
| 251 | +project_path = Path(project_path_input).expanduser().resolve() |
| 252 | + |
| 253 | +if not project_path.exists() or not project_path.is_dir(): |
| 254 | + st.error("The provided path does not exist or is not a directory.") |
| 255 | + st.stop() |
| 256 | + |
| 257 | +st.session_state.project_path = str(project_path) |
216 | 258 |
|
217 | | -project_path = Path.cwd().resolve() |
218 | 259 |
|
219 | 260 | if "selected_path" not in st.session_state: |
220 | 261 | st.session_state.selected_path = None |
@@ -275,35 +316,48 @@ def render_explorer(path: Path, level: int = 0, prefix: str = "") -> None: |
275 | 316 | st.rerun() |
276 | 317 |
|
277 | 318 |
|
278 | | -st.markdown( |
279 | | - f""" |
280 | | - <div style=" |
281 | | - margin-bottom: 0.6rem; |
282 | | - padding-left: 0.1rem; |
283 | | - "> |
284 | | - <h1 style=" |
285 | | - margin-bottom:0rem; |
286 | | - "> |
287 | | - 🧭 FSS Navigator |
288 | | - </h1> |
289 | | - </div> |
290 | | - """, |
291 | | - unsafe_allow_html=True, |
292 | | -) |
293 | 319 |
|
294 | 320 | left_col, right_col = st.columns([1.2, 3], gap="small") |
295 | 321 |
|
296 | 322 | with left_col: |
297 | 323 | left_panel = st.container(key="left_panel") |
298 | 324 | with left_panel: |
299 | 325 | st.markdown("### 📂 Directory structure") |
300 | | - render_explorer(project_path) |
| 326 | + |
| 327 | + search_query = st.text_input( |
| 328 | + "Search in selected directory", |
| 329 | + placeholder="Search files or folders...", |
| 330 | + key="directory_search", |
| 331 | + label_visibility="collapsed", |
| 332 | + ) |
| 333 | + |
| 334 | + if search_query: |
| 335 | + search_results = search_directory(project_path, search_query) |
| 336 | + |
| 337 | + st.caption(f"{len(search_results)} result(s) found") |
| 338 | + |
| 339 | + for result in search_results[:100]: |
| 340 | + icon = "📁" if result.is_dir() else "📄" |
| 341 | + relative_result = result.relative_to(project_path) |
| 342 | + |
| 343 | + if st.button( |
| 344 | + f"{icon} {relative_result}", |
| 345 | + key=f"search_{result}", |
| 346 | + use_container_width=True, |
| 347 | + ): |
| 348 | + st.session_state.selected_path = str(result) |
| 349 | + st.rerun() |
| 350 | + |
| 351 | + if len(search_results) > 100: |
| 352 | + st.warning("Showing first 100 results only.") |
| 353 | + else: |
| 354 | + render_explorer(project_path) |
301 | 355 |
|
302 | 356 | with right_col: |
303 | 357 | right_panel = st.container(key="right_panel") |
304 | 358 | with right_panel: |
305 | 359 | st.markdown("### 👁️ Preview panel") |
306 | | - |
| 360 | + |
307 | 361 | selected_path_value = st.session_state.selected_path |
308 | 362 |
|
309 | 363 | if selected_path_value is None: |
@@ -346,11 +400,11 @@ def render_explorer(path: Path, level: int = 0, prefix: str = "") -> None: |
346 | 400 |
|
347 | 401 | if suffix in ["md", "rmd"]: |
348 | 402 | content = read_text_file(str(selected_path), max_chars=None) |
349 | | - st.markdown(content, unsafe_allow_html=False) |
| 403 | + st.markdown(content, unsafe_allow_html=True) |
350 | 404 |
|
351 | 405 | elif suffix == "html": |
352 | 406 | content = read_text_file(str(selected_path), max_chars=None) |
353 | | - components.html(content, height=800, scrolling=True) |
| 407 | + st.html(content) |
354 | 408 |
|
355 | 409 | elif suffix in [ |
356 | 410 | "py", |
@@ -383,32 +437,44 @@ def render_explorer(path: Path, level: int = 0, prefix: str = "") -> None: |
383 | 437 | for page_number, image in pdf_pages: |
384 | 438 | st.write(f"Page {page_number}") |
385 | 439 | st.image(image, width="stretch") |
| 440 | + |
| 441 | + pdf_links = extract_pdf_links(str(selected_path)) |
| 442 | + |
| 443 | + if pdf_links: |
| 444 | + st.write("### Links detected in PDF") |
| 445 | + |
| 446 | + for link in pdf_links: |
| 447 | + page_label = f"Page {link['page']}" |
| 448 | + |
| 449 | + if link["uri"]: |
| 450 | + st.markdown(f"- {page_label}: [{link['uri']}]({link['uri']})") |
| 451 | + |
| 452 | + elif link["target_page"] is not None: |
| 453 | + st.markdown( |
| 454 | + f"- {page_label}: internal PDF link to page {link['target_page'] + 1}" |
| 455 | + ) |
386 | 456 |
|
387 | 457 | except Exception as error: |
388 | 458 | st.error(f"Could not render PDF: {error}") |
389 | 459 |
|
390 | 460 | elif preview_type == "notebook": |
391 | 461 | try: |
392 | 462 | notebook_html = render_notebook_html(str(selected_path)) |
393 | | - components.html(notebook_html, height=900, scrolling=True) |
| 463 | + st.html(notebook_html) |
394 | 464 | except Exception as error: |
395 | 465 | st.error(f"Could not render notebook: {error}") |
396 | 466 |
|
397 | 467 | elif preview_type == "docx": |
398 | 468 | try: |
399 | | - docx_text = read_docx_file(str(selected_path)) |
400 | | - st.text_area( |
401 | | - "DOCX content", |
402 | | - value=docx_text, |
403 | | - height=800, |
404 | | - ) |
| 469 | + docx_html = render_docx_as_html(str(selected_path)) |
| 470 | + st.html(docx_html) |
405 | 471 | except Exception as error: |
406 | 472 | st.error(f"Could not preview DOCX file: {error}") |
407 | 473 |
|
408 | 474 | elif preview_type == "pptx": |
409 | 475 | try: |
410 | | - pptx_text = read_pptx_file(str(selected_path)) |
411 | | - st.markdown(pptx_text) |
| 476 | + pptx_html = render_pptx_as_html(str(selected_path)) |
| 477 | + st.html(pptx_html) |
412 | 478 | except Exception as error: |
413 | 479 | st.error(f"Could not preview PPTX file: {error}") |
414 | 480 |
|
|
0 commit comments