Skip to content

Folders Page ​

File Information ​

  • Path: app/pages/[brand_name]/folders/[id]/index.vue
  • Route: /{brand_name}/folders/{id}
  • Purpose: Display folder contents with assets and subfolders, supporting grid/list views, sorting, selection, and bulk operations
  • Layout: collage-layout
  • Middleware: check-url

Overview ​

The Folders page displays the contents of a specific folder, including both assets (files) and subfolders. It provides comprehensive folder management features including view mode toggling, sorting, bulk selection, sharing, downloading, and navigation through the folder hierarchy.

Key Features ​

  1. Folder Contents Display

    • Assets (files) and subfolders in grid or list view
    • Folder metadata (name, description, counts)
    • Breadcrumb navigation
    • Empty state handling
  2. View Modes

    • Grid view with mosaic layout
    • List view with sortable columns
    • View mode preference caching
    • Smooth transitions between views
  3. Sorting

    • Sort by Name, Date Modified, Date Added, File Size, File Type
    • Ascending/descending toggle
    • Visual sort indicators
    • Server-side sorting support
  4. Selection & Bulk Actions

    • Single and multi-select (Shift+Click support)
    • Select all functionality
    • Bulk download
    • Bulk share
    • Selection count display
  5. Folder Actions

    • Share folder
    • Download folder (all contents)
    • Folder options menu
  6. Infinite Scroll Pagination

    • Automatic loading on scroll
    • Skeleton loaders
    • Scroll position management

Components Used ​

  • BreadCrumbs - Breadcrumb navigation component
  • FolderCard - Folder card for grid view
  • AssetCard - Asset card for grid view
  • ShareAssetDialog - Share dialog component
  • AsyncIcon - Async icon component
  • v-skeleton-loader - Skeleton loaders

Composables Used ​

  • useFolderDetails() - Folder data management (app/composables/core/pages/useFolderDetails.ts)
  • useFolders() - Folder operations (app/composables/core/pages/useFolders.ts)
  • useDownloadStore() - Download store
  • useSnackbar() - Snackbar notifications
  • useFileType() - File type utilities
  • useSelection() - Selection utilities

Data Properties ​

Reactive Data (from useFolderDetails composable) ​

typescript
{
  folder: Folder | null,              // Current folder data
  folderAssets: any[],                // Folder contents (assets + subfolders)
  loading: boolean,                   // Loading state
  contentLoading: boolean,            // Initial content loading
  loadMore: boolean,                  // Load more pagination state
  viewMode: 'list' | 'grid',         // Current view mode
  pageScrolling: boolean,            // Page scrolling state
  selectedItemsCount: number,        // Selected items count
  selected_items: any[],             // Selected items array
  files2share: any[],                 // Files to share
  folders2share: any[],              // Folders to share
  shareDialog: boolean,               // Share dialog visibility
  downloadFld: boolean,               // Folder download in progress
  download_all: boolean,              // Bulk download in progress
  select_all: boolean,                // Select all state
  sort_loader: string,                // Active sort column loader
  showMoreDetails: boolean,           // Description expand state
  truncatedDescription: string,        // Truncated description
  breadcrumbChildLnk: any[],          // Breadcrumb links
  mainFolderMenu: any[],              // Folder menu options
  listItemMenu: any[],                // List item menu options
  showArrowSort: Record<string, boolean>, // Sort arrow visibility
  hover_name: boolean,                 // Name column hover
  hover_modified_at: boolean,         // Modified date hover
  hover_created_at: boolean,          // Created date hover
  hover_file_size: boolean,           // File size hover
  hover_file_type: boolean,           // File type hover
  fileCardKey: number,                // File card key for reactivity
  folderCardKey: number,              // Folder card key for reactivity
  selectAllClass: string,              // Select all checkbox class
  share_folder: boolean,               // Share folder flag
  download_items: any[]                // Items to download
}

Computed Properties ​

workspaceId ​

Returns workspace ID from helpers.

typescript
computed(() => getWorkspaceId() || "")

getShareTitle ​

Returns share title based on selection.

typescript
computed(() => {
  if (share_folder.value) return folder.value.folder_name || ""
  if (folders2share.value.length === 1 && !files2share.value.length)
    return folders2share.value[0].folder_name || ""
  if (files2share.value.length === 1 && !folders2share.value.length)
    return files2share.value[0]?.display_file_name?.replace(
      new RegExp(`\\.${files2share.value[0]?.value?.file_type}$`, "i"),
      ""
    ) || ""
  return ""
})

getShareDescription ​

Returns share description based on selection.

typescript
computed(() => {
  if (share_folder.value) return folder.value?.description || ""
  if (folders2share.value.length === 1 && !files2share.value.length)
    return folders2share.value[0]?.description || ""
  if (files2share.value.length === 1 && !folders2share.value.length)
    return files2share.value[0]?.description || ""
  return ""
})

selectAllClass ​

Returns CSS class for select all checkbox.

typescript
computed(() => 
  !select_all.value && selectedItemsCount.value ? "semi-selected" : ""
)

truncatedDescription ​

Returns truncated folder description.

typescript
computed(() => {
  if (!folder.value || !folder.value.description) return ""
  if (showMoreDetails.value && folder.value.description.length > 250) {
    return folder.value.description.slice(0, 250)
  }
  return folder.value.description
})

Returns breadcrumb navigation links.

typescript
computed(() => {
  const retArr: any[] = []
  const addToBreadcrumb = (currentFolder: Folder | null) => {
    if (!currentFolder?.parent) return
    addToBreadcrumb(currentFolder.parent)
    if (currentFolder.parent.folder_name && currentFolder.parent.id) {
      retArr.push({
        name: currentFolder.parent.folder_name,
        url: {
          name: "brand_name-folders-id",
          params: {
            brand_name: getBrandName(),
            id: currentFolder.parent.id,
          },
        },
      })
    }
  }
  if (folder.value) {
    addToBreadcrumb(folder.value)
    if (folder.value.folder_name) {
      retArr.push({
        name: folder.value.folder_name,
        url: null,
      })
    }
  }
  return retArr
})

selectedItemsCount ​

Returns count of selected items.

typescript
computed(() => {
  if (internal_select_all.value && select_all.value) {
    return total_assets.value
  } else if (internal_select_all.value) {
    const notSelected = folderAssets.value.filter(
      (obj) => !obj.is_selected && !obj.is_uncategorised
    ).length
    return total_assets.value - notSelected
  } else {
    return selected_items.value.length
  }
})

mainFolderMenu ​

Returns folder menu options.

typescript
computed(() => [
  {
    icon: "shareIcon",
    label: "Share",
    clickEvent: () => {
      share_folder.value = true
      shareDialog.value = true
    },
    disable: false,
  },
  {
    icon: "downloadIcon",
    label: downloadFld.value ? "Download will start soon…" : "Download",
    clickEvent: () => downloadFile("download_all"),
    disable: downloadFld.value,
  },
])

Methods ​

fetchFolder(flag = "no-sort") ​

Fetches folder contents from API.

typescript
async function fetchFolder(flag = "no-sort"): Promise<void> {
  try {
    if (flag === "no-sort") {
      if (loadMore.value || currentPage.value >= lastPage.value) return
      currentPage.value += 1
      loadMore.value = currentPage.value > 1
      loading.value = currentPage.value === 1
    }
    
    const params = {
      sort_value: page_sort_value.value,
      sort_by: page_sort_by.value,
      workspace_id: workspaceId.value,
      category_id: route.params.id as string,
      page: currentPage.value,
    }
    
    const { data } = await fetchFolderDetails(params)
    
    const new_assets: any[] = data.assets_with_folder.data.map(
      (asset: any) => ({
        ...asset,
        is_selected: internal_select_all.value,
        is_downloading: false,
        isLoading: true,
      })
    )
    
    lastPage.value = data.assets_with_folder.last_page || 0
    total_assets.value = data.assets_with_folder.total || 0
    
    // Process and update folderAssets
    if (flag === "reload") {
      // Reload logic with scroll position handling
    } else {
      folderAssets.value.push(...processedNewAssets)
    }
    
    // Update folder metadata
    if (folder.value) {
      folder.value.sub_category_count = data.totalCounts?.folders || 0
      folder.value.total_assets = data.totalCounts?.assets || 0
    }
  } catch (error) {
    console.error("Error in fetchFolder:", error)
    snackbar.showError(getErrorMessage(error))
  } finally {
    loading.value = false
    loadMore.value = false
    sort_loader.value = ""
  }
}

Parameters:

  • flag: string - Operation flag: "no-sort", "reload", or sort field name

Flow:

  1. Checks pagination limits
  2. Builds API parameters
  3. Fetches folder details
  4. Processes assets with cache
  5. Updates folder metadata
  6. Handles errors

handleScroll(flag = "main") ​

Handles scroll events for infinite scroll.

typescript
async function handleScroll(flag = "main"): Promise<void> {
  try {
    const container =
      flag === "list" ? listContainer.value : scrollContainer.value
    if (!container) return
    
    const scrollTop = container.scrollTop
    const containerHeight = container.scrollHeight - container.clientHeight
    const scrollPercent = (scrollTop / containerHeight) * 100
    
    if (scrollPercent >= 85 && folderAssets.value.length) {
      if (loading.value || loadMore.value) return
      await fetchFolder()
    } else if (scrollTop >= 36 && folderAssets.value.length) {
      pageScrolling.value = true
    } else {
      pageScrolling.value = false
    }
  } catch (error) {
    snackbar.showError(getErrorMessage(error))
  }
}

Triggers: Loads more items when scroll reaches 85% of container height.

toggleSelectAll(flag = true) ​

Toggles select all state.

typescript
function toggleSelectAll(flag = true): void {
  if (!flag) select_all.value = false
  internal_select_all.value = select_all.value
  selected_items.value = select_all.value ? [...folderAssets.value] : []
  folderAssets.value = folderAssets.value.map((file) => ({
    ...file,
    is_selected: select_all.value,
  }))
  lastSelectedIndex.value = -1
}

toggleSelect(file, index, event) ​

Toggles individual item selection with Shift+Click support.

typescript
function toggleSelect(file: any, index: number, event: any): void {
  try {
    const context = {
      folderAssets: folderAssets.value,
      selected_items: selected_items.value,
      folders: folderAssets.value.filter((f: any) => f.parent_id),
      lastPage: lastPage.value,
      currentPage: currentPage.value,
    }
    toggleSelection(context, file, index, event, "folders")
    selected_items.value = context.selected_items
  } catch (error) {
    snackbar.showError(getErrorMessage(error))
    lastSelectedIndex.value = -1
  }
}

changeView(mode: 'list' | 'grid') ​

Changes view mode and caches preference.

typescript
function changeView(mode: 'list' | 'grid') {
  viewMode.value = mode
  setCachedViewMode(mode)
}

handleSort(field: string) ​

Handles column sorting.

typescript
function handleSort(field: string) {
  sort_loader.value = field
  page_sort_value.value = field
  page_sort_by.value = page_sort_by.value === "ASC" ? "DESC" : "ASC"
  
  // Update arrow visibility
  for (const key in showArrowSort.value) {
    showArrowSort.value[key] = false
  }
  showArrowSort.value[field] = true
  
  await fetchFolder(field)
}

downloadFile(flag = "selected") ​

Handles file/folder downloads.

typescript
async function downloadFile(flag = "selected"): Promise<void> {
  try {
    const isDownloadAll = flag === "download_all"
    
    if (isDownloadAll || (hasDownloadItems && items.length === 1 && items[0].parent_id)) {
      // Download folder(s)
      await downloadStore.downloadMultipleFiles({
        files: [],
        folders: folderIds,
        download_name: folder.value!.folder_name,
      })
    } else if (items.length === 1 && !items[0].parent_id) {
      // Download single file
      const { isImage } = useFileType(singleItem || {})
      if (isImage.value) {
        await downloadStore.downloadImageAsset({
          assetId: String(singleItem.id),
          fileName: singleItem.display_file_name,
          fileType: singleItem.file_type,
        })
      } else {
        downloadStore.downloadFile({
          id: String(singleItem.id),
          url: singleItem.download_url,
          name: singleItem.display_file_name,
        })
      }
    } else if (hasSelectedItems) {
      // Download multiple files
      await downloadStore.downloadMultipleFiles({
        files: fileIds,
        folders: [],
        download_name: `download-files-${Date.now()}`,
      })
    }
  } catch (error) {
    snackbar.showError(getErrorMessage(error))
  }
}

openShareDialog(items: any[]) ​

Opens share dialog with selected items.

typescript
function openShareDialog(items: any[]) {
  const folders = items.filter((item) => item.parent_id)
  const files = items.filter((item) => !item.parent_id)
  
  if (folders.length) {
    folders2share.value = folders
    share_folder.value = folders.length === 1 && !files.length
  }
  if (files.length) {
    files2share.value = files
  }
  
  shareDialog.value = true
}

closeShareAssetDialog() ​

Closes share dialog and resets state.

typescript
function closeShareAssetDialog() {
  shareDialog.value = false
  share_folder.value = false
  files2share.value = []
  folders2share.value = []
}

onFolderDownload(folder: any) ​

Handles folder download from card.

typescript
function onFolderDownload(folder: any) {
  download_items.value = [folder]
  downloadFile()
}

onAssetCardSelect(file, index, event) ​

Handles asset card selection.

typescript
function onAssetCardSelect(file: any, index: number, event: any) {
  toggleSelect(file, index, event)
}

onFolderCardSelect(folder, index, event) ​

Handles folder card selection.

typescript
function onFolderCardSelect(folder: any, index: number, event: any) {
  toggleSelect(folder, index, event)
}

onBulkDownload() ​

Handles bulk download action.

typescript
function onBulkDownload() {
  downloadFile("selected")
}

listItemClick(payload) ​

Handles list item menu actions.

typescript
function listItemClick(payload: { file: any, label: string }) {
  const { file, label } = payload
  
  if (label === "ShareIcon") {
    openShareDialog([file])
  } else if (label === "DownloadIcon") {
    download_items.value = [file]
    downloadFile()
  }
}

isActiveArrow(field: string) ​

Returns CSS class for active sort arrow.

typescript
function isActiveArrow(field: string): string {
  if (showArrowSort.value[field] && page_sort_by.value === "DESC") {
    return "active"
  }
  return ""
}

Utility Methods ​

  • getSrcPathRef(file) - Returns image source path
  • formatDate(date) - Formats date for display
  • toHumanlySize(size) - Converts bytes to human-readable size
  • trackFolderClick(folder) - Tracks folder click analytics
  • trackAssetClick(asset) - Tracks asset click analytics

User Interactions ​

  1. Folder Navigation

    • Click folders to navigate to subfolders
    • Use breadcrumbs to navigate up hierarchy
    • Back navigation support
  2. View Modes

    • Toggle grid/list view
    • View mode preference cached
    • Smooth transitions
  3. Sorting

    • Sort by Name, Date Modified, Date Added, File Size, File Type
    • Click column headers to sort
    • Visual sort indicators
  4. Selection

    • Single click to select
    • Shift+Click for range selection
    • Select all functionality
    • Selection count display
  5. Bulk Actions

    • Download selected items
    • Share selected items
    • Remove selection
  6. Item Actions

    • Download files/folders
    • Share files/folders
    • Navigate to asset details

Real-time Features ​

  • Folder asset counts cached to avoid repeated API calls
  • Real-time updates via Pusher (if configured)

Styling ​

  • Uses custom CSS from assets/scss/custom.scss
  • Responsive breakpoints for mobile/tablet/desktop
  • Skeleton loaders for loading states
  • Grid/list view styling
  • Breadcrumb navigation styling
  • Custom scrollbar styling

Watchers ​

Folder ID Watcher ​

Watches route parameter changes to reload folder.

typescript
watch(
  () => route.params.id,
  async (newId) => {
    if (newId) {
      await fetchFolder("reload")
    }
  }
)

Lifecycle Hooks ​

onMounted ​

Initializes folder data and sets up event listeners.

typescript
onMounted(async () => {
  await fetchFolder()
  contentLoading.value = false
  
  // Listen to download events
  mittBus.on("download-all-folder", () => {
    download_all.value = false
    downloadFld.value = false
    // Reset downloading states
  })
  
  mittBus.on("download-file-folder", (asset_id: string | number) => {
    const found = folderAssets.value.findIndex((obj) => obj.id === asset_id)
    if (found > -1) folderAssets.value[found].is_downloading = false
  })
})

onUnmounted ​

Cleans up event listeners.

typescript
onUnmounted(() => {
  mittBus.off("download-all-folder")
  mittBus.off("download-file-folder")
})

API Endpoints ​

Get Folder Details ​

  • Endpoint: GET /folders/{id} (via fetchFolderDetails())
  • Parameters:
    typescript
    {
      category_id: string,
      workspace_id: string,
      page: number,
      sort_value: string,
      sort_by: 'ASC' | 'DESC'
    }
  • Response:
    typescript
    {
      breadcrumb: Folder,
      assets_with_folder: {
        data: Asset[],
        last_page: number,
        total: number
      },
      totalCounts: {
        folders: number,
        assets: number
      },
      all_assets_ids: number[],
      all_folder_ids: number[]
    }

User Flow ​

  1. User navigates to folder page via URL or breadcrumb
  2. Page fetches folder details and contents
  3. User can:
    • Toggle grid/list view
    • Sort by columns
    • Select items (single or multi-select with Shift)
    • Download files/folders
    • Share files/folders
    • Navigate to subfolders
    • Scroll for more items
  4. Infinite scroll loads more items automatically
  5. Bulk actions work on selected items

Performance Optimizations ​

  1. Asset Caching: Folder asset counts cached to avoid repeated API calls
  2. Pagination: Loads items in pages (default 36 per page)
  3. Scroll Throttling: Scroll events handled efficiently
  4. Lazy Loading: Images loaded on demand
  5. View Mode Caching: User preference cached in localStorage

Notes for Development ​

  • Folder hierarchy supports unlimited nesting
  • Breadcrumbs generated from folder parent chain
  • Selection state persists across pagination
  • Download progress tracked per item
  • Share dialog supports both files and folders
  • Empty state shown when folder has no contents
  • Error handling redirects to folders list if folder not found