Appearance
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 ​
Folder Contents Display
- Assets (files) and subfolders in grid or list view
- Folder metadata (name, description, counts)
- Breadcrumb navigation
- Empty state handling
View Modes
- Grid view with mosaic layout
- List view with sortable columns
- View mode preference caching
- Smooth transitions between views
Sorting
- Sort by Name, Date Modified, Date Added, File Size, File Type
- Ascending/descending toggle
- Visual sort indicators
- Server-side sorting support
Selection & Bulk Actions
- Single and multi-select (Shift+Click support)
- Select all functionality
- Bulk download
- Bulk share
- Selection count display
Folder Actions
- Share folder
- Download folder (all contents)
- Folder options menu
Infinite Scroll Pagination
- Automatic loading on scroll
- Skeleton loaders
- Scroll position management
Components Used ​
BreadCrumbs- Breadcrumb navigation componentFolderCard- Folder card for grid viewAssetCard- Asset card for grid viewShareAssetDialog- Share dialog componentAsyncIcon- Async icon componentv-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 storeuseSnackbar()- Snackbar notificationsuseFileType()- File type utilitiesuseSelection()- 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
})breadcrumbChildLnk ​
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:
- Checks pagination limits
- Builds API parameters
- Fetches folder details
- Processes assets with cache
- Updates folder metadata
- 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 pathformatDate(date)- Formats date for displaytoHumanlySize(size)- Converts bytes to human-readable sizetrackFolderClick(folder)- Tracks folder click analyticstrackAssetClick(asset)- Tracks asset click analytics
User Interactions ​
Folder Navigation
- Click folders to navigate to subfolders
- Use breadcrumbs to navigate up hierarchy
- Back navigation support
View Modes
- Toggle grid/list view
- View mode preference cached
- Smooth transitions
Sorting
- Sort by Name, Date Modified, Date Added, File Size, File Type
- Click column headers to sort
- Visual sort indicators
Selection
- Single click to select
- Shift+Click for range selection
- Select all functionality
- Selection count display
Bulk Actions
- Download selected items
- Share selected items
- Remove selection
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}(viafetchFolderDetails()) - 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 ​
- User navigates to folder page via URL or breadcrumb
- Page fetches folder details and contents
- 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
- Infinite scroll loads more items automatically
- Bulk actions work on selected items
Performance Optimizations ​
- Asset Caching: Folder asset counts cached to avoid repeated API calls
- Pagination: Loads items in pages (default 36 per page)
- Scroll Throttling: Scroll events handled efficiently
- Lazy Loading: Images loaded on demand
- 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
Related Documentation ​
- useFolderDetails Composable - Folder details composable
- Folders API - Folder API endpoints
- Download Store - Download state management
- ShareAssetDialog Component - Share dialog component
- FolderCard Component - Folder card component
- AssetCard Component - Asset card component