Skip to content

Conversation

@zudsniper
Copy link

@zudsniper zudsniper commented Oct 3, 2025

Summary

  • Introduces a significant refactor of the mobile file viewer components
  • Adds FileBrowser.vue for improved file browsing experience
  • Updates FilesFolderPage.vue with UI and logic enhancements
  • Adds a new package-lock.json with updated dependencies reflecting the new features

Changes

Components

  • FileBrowser.vue: New component added to provide a performant and user-friendly file browsing interface on mobile devices
  • FilesFolderPage.vue: Refactored to integrate with FileBrowser, improve folder navigation and file display

Package Management

  • Added package-lock.json with locked dependencies to ensure reproducible builds and updated packages necessary for refactoring

Code Quality

  • Removed obsolete code and optimized existing logic for responsiveness and maintainability
  • Improved component state management and lifecycle handling

Test plan

  • Verify file browsing on mobile devices functions smoothly
  • Confirm navigation between folders and files is working as expected
  • Check that files are rendered correctly in the new FileBrowser component
  • Validate the new dependency lockfile installs and resolves packages properly
  • Run existing unit and integration tests to ensure no regressions
  • Manual exploratory testing for UI and UX on multiple mobile screen sizes

🌿 Generated by Terry


ℹ️ Tag @terragon-labs to ask questions and address PR feedback

📎 Task: https://www.terragonlabs.com/task/f4e09764-3cea-4fec-9ccd-3ac1bbf43b82

Summary by CodeRabbit

  • New Features
    • Adds a full-featured file browser with grid and list views, breadcrumbs, view toggle, file preview, contextual per-item actions, and mobile action sheet.
    • Supports drag-and-drop moves, multi-select (keyboard/touch), bulk actions (move, delete, refresh), downloads/sharing, and persisted view preference.
  • Refactor
    • Files page simplified to embed the new file browser, replacing the previous multi-component listing UI.
  • Accessibility
    • Improved focus handling, keyboard navigation, and responsive/mobile behavior.

…nents

- Added new package-lock.json for dashboard package
- Substantial updates to FileBrowser.vue and FilesFolderPage.vue adding features and refactoring
- Large number of insertions and deletions reflect dependency management and component improvements

Co-authored-by: terragon-labs[bot] <terragon-labs[bot]@users.noreply.github.com>
@coderabbitai
Copy link

coderabbitai bot commented Oct 3, 2025

Walkthrough

Adds a new FileBrowser Vue component implementing grid/list views, selection (mouse/keyboard/touch), drag-and-drop, context menus, routing-based navigation/preview, and bulk file operations. Replaces the previous table-driven files UI in FilesFolderPage.vue with a single FileBrowser instance and removes the old page-level file management subcomponents and logic.

Changes

Cohort / File(s) Summary
New component: File browser UI
packages/dashboard/src/components/files/FileBrowser.vue
Adds a comprehensive FileBrowser SFC exporting FileBrowser (props: bucket required, folder optional) providing grid/list views, breadcrumbs, selection (multi-select, shift/ctrl, touch long-press), drag-and-drop move, per-item context menus, bulk actions, routing integration, previews, and persistence of view mode.
Page integration and simplification
packages/dashboard/src/pages/files/FilesFolderPage.vue
Replaces the legacy table/list UI and many subcomponents with a single <FileBrowser> usage. Removes prior data/methods/watchers/lifecycle logic, DragAndDrop and individual dialogs/components from the page public API, and simplifies scoped styling to a minimal page wrapper.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Router
  participant FilesPage as FilesFolderPage
  participant FileBrowser
  participant Services as Store/Services

  User->>Router: Navigate to /files/:bucket/:folder?
  Router->>FilesPage: mount with route props
  FilesPage->>FileBrowser: render with bucket, folder
  FileBrowser->>Services: fetch items for bucket/folder
  Services-->>FileBrowser: return items
  FileBrowser-->>User: render grid/list

  rect rgba(200,230,255,0.15)
    note right of FileBrowser: Navigation & preview
    User->>FileBrowser: click breadcrumb/folder
    FileBrowser->>Router: router.push(new folder)
    Router->>FileBrowser: update props
    FileBrowser->>Services: refetch items
  end

  rect rgba(220,255,220,0.15)
    note right of FileBrowser: Selection & bulk ops
    User->>FileBrowser: select items / keyboard shortcuts
    User->>FileBrowser: trigger Move/Delete/Refresh
    FileBrowser->>Services: perform operations
    Services-->>FileBrowser: results / errors
  end

  rect rgba(255,240,200,0.15)
    note right of FileBrowser: Drag-and-drop
    User->>FileBrowser: drag items onto folder
    FileBrowser->>Services: move items (progress)
    Services-->>FileBrowser: success/error
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

In burrows bright I twitch and stare,
New grids and lists hang in the air.
I hop on crumbs, I drag and share,
With menus, previews, loads to spare.
A rabbit’s cheer for files made fair. 🐇📁

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly summarizes the main change by indicating a refactoring of the mobile file viewer and highlighting the introduction of the enhanced FileBrowser component and updated folder page, aligning with the primary objectives of this changeset.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch terragon/refactor-file-viewer-mobile-vujdy7

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ddcd4fc and 855e334.

⛔ Files ignored due to path filters (1)
  • packages/dashboard/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • packages/dashboard/src/components/files/FileBrowser.vue (1 hunks)
  • packages/dashboard/src/pages/files/FilesFolderPage.vue (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: Build
packages/dashboard/src/pages/files/FilesFolderPage.vue

[warning] 11-11: assist/source/organizeImports: The imports and exports are not sorted. Safe fix: Organize Imports (Biome).

packages/dashboard/src/components/files/FileBrowser.vue

[error] 402-402: lint/correctness/noUnusedFunctionParameters: Unused parameter 'emit' in setup(props, { emit }).


[error] 566-566: lint/correctness/noUnusedFunctionParameters: Unused parameters might be the result of an incomplete refactoring in handleTouchStart(event, item, index).


[error] 583-583: lint/correctness/noUnusedFunctionParameters: Unused parameter 'event' in handleTouchEnd(event).


[error] 655-655: lint/correctness/noUnusedFunctionParameters: Unused parameter 'folder' in getItemCount(folder).


[error] 660-660: lint/correctness/noUnusedFunctionParameters: Unused parameter 'event' in showContextMenu(event, item).


[error] 701-701: lint/correctness/noUnusedFunctionParameters: Unused parameter 'event' in handleDragLeave(event).

Comment on lines 647 to 653
const handleImageError = (event, item) => {
// Replace broken image with icon
event.target.style.display = 'none'
const icon = document.createElement('div')
icon.innerHTML = `<q-icon name="${item.icon}" color="${item.color}" size="48px" />`
event.target.parentElement.appendChild(icon)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace the DOM injection fallback for thumbnails.

Appending a literal <q-icon> string via innerHTML never instantiates the Quasar component, so users get an empty preview when thumbnails fail. Track the error state on the item (or a local map) and let the template render the existing icon branch instead of manipulating the DOM.

One option:

-    const handleImageError = (event, item) => {
-      // Replace broken image with icon
-      event.target.style.display = 'none'
-      const icon = document.createElement('div')
-      icon.innerHTML = `<q-icon name="${item.icon}" color="${item.color}" size="48px" />`
-      event.target.parentElement.appendChild(icon)
-    }
+    const handleImageError = (event, item) => {
+      event.target.style.display = 'none'
+      item.thumbnailError = true
+    }

and update the template to gate the <img> with && !item.thumbnailError.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleImageError = (event, item) => {
// Replace broken image with icon
event.target.style.display = 'none'
const icon = document.createElement('div')
icon.innerHTML = `<q-icon name="${item.icon}" color="${item.color}" size="48px" />`
event.target.parentElement.appendChild(icon)
}
const handleImageError = (event, item) => {
event.target.style.display = 'none'
item.thumbnailError = true
}

@zudsniper zudsniper self-assigned this Oct 3, 2025
- Fixed unused function parameters in FileBrowser.vue
- Reorganized imports to follow Biome sorting rules
- Prefixed unused parameters with underscore where needed
- Removed unused emit parameter from setup function

All lint checks now pass successfully.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
packages/dashboard/src/components/files/FileBrowser.vue (2)

597-610: ⚠️ Regression: Restore tap-to-open for files on mobile.

handleTouchEnd only opens folders on short taps (line 606), so tapping files on mobile never launches the preview. This regresses the core "mobile file viewer" functionality. Please treat short taps the same as desktop double-clicks by calling openItem for any item type.

Apply this diff:

   const touchDuration = Date.now() - touchStartTime.value;
   if (touchDuration < 500 && !selectionMode.value) {
     // Short tap - open item
-    if (currentItem.value?.type === "folder") {
+    if (currentItem.value) {
       openItem(currentItem.value);
     }
   }

663-669: ⚠️ Replace DOM injection fallback for thumbnails.

Appending a literal <q-icon> string via innerHTML never instantiates the Quasar component, so users see an empty preview when thumbnails fail. Track the error state on the item (or a local reactive map) and let the template render the existing icon branch instead of manipulating the DOM directly.

One option:

 const handleImageError = (event, item) => {
-  // Replace broken image with icon
   event.target.style.display = "none";
-  const icon = document.createElement("div");
-  icon.innerHTML = `<q-icon name="${item.icon}" color="${item.color}" size="48px" />`;
-  event.target.parentElement.appendChild(icon);
+  item.thumbnailError = true;
 }

Then update the template (lines 86-101) to conditionally render the <img> only when !item.thumbnailError:

 <div class="file-card-preview">
   <img
-    v-if="item.type === 'file' && isMediaFile(item.name)"
+    v-if="item.type === 'file' && isMediaFile(item.name) && !item.thumbnailError"
     :src="getThumbnailUrl(item, bucket)"
     :alt="item.name"
     class="file-thumbnail-img"
     loading="lazy"
     @error="handleImageError($event, item)"
   />
   <q-icon
     v-else
     :name="item.icon"
     :color="item.color"
     size="48px"
   />
 </div>

Apply the same change to the list view thumbnail at lines 192-199.

🧹 Nitpick comments (3)
packages/dashboard/src/components/files/FileBrowser.vue (3)

435-435: Consider making showThumbnailsInList configurable.

showThumbnailsInList is declared and always set to true (line 435) but is never modified. If this is intended as a user preference, consider either:

  1. Persisting it in localStorage like viewMode, or
  2. Exposing it as a prop for parent control, or
  3. Removing it if thumbnails should always be shown in list view.

67-67: Remove unnecessary $event from template bindings.

Several template event bindings pass $event to handlers that don't use it (indicated by the _event parameter prefix):

  • Line 67, 173: @touchstart="handleTouchStart($event, item, index)"@touchstart="handleTouchStart(item, index)"
  • Line 73, 179: @dragleave="handleDragLeave($event)"@dragleave="handleDragLeave"
  • Line 121, 226: @click.stop="showContextMenu($event, item)"@click.stop="showContextMenu(item)"

This aligns the template with the handler signatures and removes the confusion caused by passing unused parameters.

Also applies to: 73-73, 121-121, 173-173, 179-179, 226-226


6-12: Consider accessibility improvements for interactive elements.

Interactive elements like breadcrumb items (lines 6-12), grid view cards (lines 55-137), and list rows (lines 161-243) use divs with click handlers. While keyboard navigation is implemented in JavaScript, consider enhancing accessibility by:

  1. Using <button> elements for breadcrumb items instead of divs with @click
  2. Adding role="button", tabindex="0", and aria-label attributes to file cards/rows
  3. Ensuring focus states are visually distinct (already handled in CSS with .focused class)

These changes would improve the experience for screen reader users and align with WCAG best practices.

Also applies to: 55-137, 161-243

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 855e334 and 1078732.

📒 Files selected for processing (2)
  • packages/dashboard/src/components/files/FileBrowser.vue (1 hunks)
  • packages/dashboard/src/pages/files/FilesFolderPage.vue (1 hunks)
🔇 Additional comments (21)
packages/dashboard/src/components/files/FileBrowser.vue (18)

400-408: LGTM: Props are well-defined.

The component accepts bucket (required) and folder (optional, defaults to empty string) as props. This provides a clear and minimal API for controlling the file browser location.


448-473: Verify hardcoded "Home/" in breadcrumb logic.

Line 463 removes a hardcoded "Home/" prefix from breadcrumb paths. This assumes the folder structure always starts with "Home/" which may not hold for all bucket configurations. Please verify this assumption or make the prefix configurable.

If the assumption is incorrect, consider removing the hardcoded replacement or making it dynamic based on the actual bucket/folder structure.


492-509: LGTM: fetchFiles implementation is solid.

The method correctly manages loading state, calls the API handler with appropriate parameters, and handles errors with user-friendly notifications. The use of finally ensures loading state is always cleared.


511-527: LGTM: Selection methods are straightforward and correct.

isSelected, clearSelection, and handleSelectAll all correctly manage selection state without unnecessary complexity.


529-578: LGTM: Click and context menu handlers are well-implemented.

handleItemClick correctly handles Ctrl/Cmd-click for toggling, Shift-click for range selection, and normal clicks for navigation. handleItemDoubleClick and handleContextMenu appropriately handle their respective interactions with proper mobile detection.


580-595: LGTM: Touch start handler implements long-press selection correctly.

handleTouchStart correctly implements long-press detection (500ms) to enter selection mode with haptic feedback. The timer management is sound.


612-617: LGTM: Touch move handler correctly cancels long-press.

handleTouchMove appropriately clears the touch timer when the user drags, preventing accidental selection mode activation.


684-777: LGTM: Drag-and-drop implementation is robust and user-friendly.

The drag-and-drop handlers correctly:

  • Validate readonly mode and drop targets
  • Handle multi-item dragging
  • Prevent dropping items on themselves
  • Show progress notifications during moves
  • Handle errors gracefully
  • Refresh the view after successful operations

The progress notification with percentage updates (lines 735-762) is particularly good UX for long-running operations.


780-790: LGTM: File operation delegators are concise.

handleDelete, handleRename, and handleUpdateMetadata correctly delegate to the options component with the target item, using the fallback currentItem.value when no item is provided.


792-824: LGTM: Share handler with clipboard fallback.

handleShare correctly builds URLs for both folders and files, uses the modern navigator.clipboard API, and provides user feedback for success/failure.


826-852: LGTM: Cache refresh handles folders recursively.

handleRefreshCache correctly distinguishes between files and folders, fetching folder contents recursively before opening the cache dialog. The onComplete callback ensures the view refreshes after the operation.


854-864: LGTM: Bulk operations are well-structured.

handleBulkMove and handleBulkDelete appropriately delegate to specialized dialogs/components, with handleBulkDelete correctly handling single vs. bulk deletion.


866-895: LGTM: Bulk cache refresh aggregates files correctly.

handleBulkRefreshCache correctly iterates selected items, fetching folder contents where needed, then aggregates all files before opening the cache dialog. The onComplete callback ensures both refresh and selection clearing.


897-907: LGTM: Gallery and move completion handlers are simple and correct.

handleMoveComplete and openGallery are straightforward utility methods that correctly manage state and component interactions.


910-974: LGTM: Comprehensive keyboard navigation.

handleKeyDown implements a complete set of keyboard shortcuts:

  • Arrow keys for navigation with boundary checks
  • Enter to open items
  • Space to toggle selection
  • Delete/Backspace for deletion
  • Escape to clear selection
  • Ctrl/Cmd+A to select all

All shortcuts prevent default browser behavior appropriately and handle edge cases (empty lists, focused index bounds).


977-1005: LGTM: Lifecycle management is sound.

The component correctly:

  • Persists view mode preference to localStorage
  • Fetches files on mount
  • Registers and cleans up keyboard event listeners (preventing memory leaks)
  • Handles route-based file preview using nextTick
  • Re-fetches data when folder or bucket props change

1081-1530: LGTM: Comprehensive and responsive styling.

The styles are well-organized with:

  • Clear separation between grid and list views
  • Responsive breakpoints for mobile/tablet/desktop
  • Smooth animations for state transitions
  • Accessible focus states
  • Consistent use of spacing and colors

410-410: ⚠️ Build-blocking: Remove unused emit parameter.

The setup function accepts emit in the destructured context but never uses it, causing a lint failure (noUnusedFunctionParameters). Please remove it from the signature.

Apply this diff:

-  setup(props) {
+  setup(props) {

Wait, I see the code already shows setup(props) without emit. Let me re-examine...

Actually, the provided code at line 410 shows:

setup(props) {

This is correct—no emit is present. However, the past review comment states that emit was causing a lint error. Since the current code doesn't show emit, this issue appears to have been resolved. Let me verify the other past issues.

packages/dashboard/src/pages/files/FilesFolderPage.vue (3)

1-8: LGTM: Simplified page structure.

The template is now a clean wrapper around the FileBrowser component, passing through the required bucket and folder props from the route. This delegation approach is appropriate and maintainable.


10-25: LGTM: Minimal component logic.

The script correctly imports FileBrowser, registers it, and exposes a single computed property (selectedBucket) to provide the bucket from the route. This is exactly the right level of abstraction for a page component.


27-33: LGTM: Simple page styling.

The scoped styles establish the page as a full-height flex container, allowing FileBrowser to control its own layout. This is appropriate for a wrapper component.

@zudsniper zudsniper merged commit 400c9a7 into main Oct 3, 2025
2 checks passed
@zudsniper zudsniper deleted the terragon/refactor-file-viewer-mobile-vujdy7 branch October 3, 2025 13:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants