useDownloadActivityStore
A Pinia store for tracking download progress and status for file exports (Excel, PDF, CSV, Print). This store automatically tracks downloads initiated from FtxGrid components and can be used to display download progress in your application UI (e.g., sidebar download activity).
Features
- ✅ Automatic Tracking - Downloads are automatically tracked when initiated via
FtxGrid - ✅ Progress Updates - Real-time progress tracking (if supported by API)
- ✅ Status Management - Tracks
downloading,completed, anderrorstates - ✅ Metadata Storage - Stores filename, format, table title, timestamps, and error messages
- ✅ Reactive Updates - Pinia store provides reactive updates for UI components
Installation
The store is exported from @ftx/ui:
import { useDownloadActivityStore } from '@ftx/ui';Note: The store requires Pinia to be installed and configured in your application. Make sure you have a Pinia instance set up before using this store.
Automatic Tracking
Downloads are automatically tracked when initiated through FtxGrid export/print functionality. No additional setup is required - the download helper integrates with the store automatically.
How It Works
When you use FtxGrid to export data (Excel, PDF, CSV, or Print), the download helper:
- Creates a unique download ID
- Adds the download to the store with
downloadingstatus - Updates progress percentage as data downloads (if API supports progress events)
- Marks as
completedon success orerroron error - Automatically removes completed downloads after a delay (10 seconds for PDF/Print, 2 seconds for others)
Disabling Automatic Tracking
If you need to disable tracking for a specific download, you can pass trackDownload: false to the startDownload function. However, this is an internal API and typically not needed for normal usage.
Usage
Basic Example: Display Active Downloads
<template>
<div class="download-activity">
<div v-if="hasActiveDownloads">
<h3>Active Downloads</h3>
<div v-for="download in activeDownloads" :key="download.id" class="download-item">
<div class="download-info">
<div class="filename">{{ download.filename }}</div>
<div class="table-title">{{ download.tableTitle }}</div>
</div>
<q-linear-progress
v-if="download.status === 'downloading'"
:value="download.progress"
color="primary"
/>
<q-icon
v-if="download.status === 'completed'"
name="check_circle"
color="positive"
/>
<q-icon
v-if="download.status === 'error'"
name="error"
color="negative"
/>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useDownloadActivityStore } from '@ftx/ui'
const downloadStore = useDownloadActivityStore()
// Reactive getters
const activeDownloads = computed(() => downloadStore.getActiveDownloads)
const hasActiveDownloads = computed(() => downloadStore.hasActiveDownloads)
</script>Complete Sidebar Example
<template>
<q-list class="download-activity-sidebar">
<q-item-label header>Download Activity</q-item-label>
<q-item v-if="!hasActiveDownloads" class="text-grey-6">
<q-item-section>
<q-item-label>No active downloads</q-item-label>
</q-item-section>
</q-item>
<q-item
v-for="download in activeDownloads"
:key="download.id"
class="download-item"
>
<q-item-section avatar>
<q-icon
:name="getStatusIcon(download.status)"
:color="getStatusColor(download.status)"
size="24px"
/>
</q-item-section>
<q-item-section>
<q-item-label>{{ download.filename }}</q-item-label>
<q-item-label caption>{{ download.tableTitle }}</q-item-label>
<q-linear-progress
v-if="download.status === 'downloading' && download.progress !== null"
:value="download.progress"
color="primary"
class="q-mt-sm"
/>
<div v-if="download.error" class="text-negative text-caption q-mt-xs">
{{ download.error }}
</div>
</q-item-section>
<q-item-section side>
<q-btn
flat
round
dense
icon="close"
size="sm"
@click="removeDownload(download.id)"
/>
</q-item-section>
</q-item>
</q-list>
</template>
<script setup>
import { computed } from 'vue'
import { useDownloadActivityStore } from '@ftx/ui'
const downloadStore = useDownloadActivityStore()
const activeDownloads = computed(() => downloadStore.getActiveDownloads)
const hasActiveDownloads = computed(() => downloadStore.hasActiveDownloads)
function getStatusIcon(status) {
const icons = {
'downloading': 'download',
'completed': 'check_circle',
'error': 'error'
}
return icons[status] || 'help'
}
function getStatusColor(status) {
const colors = {
'downloading': 'primary',
'completed': 'positive',
'error': 'negative'
}
return colors[status] || 'grey'
}
function removeDownload(id) {
downloadStore.removeDownload(id)
}
</script>View All Downloads (Including Completed)
<template>
<div>
<h3>All Downloads</h3>
<q-list>
<q-item v-for="download in allDownloads" :key="download.id">
<q-item-section>
<q-item-label>{{ download.filename }}</q-item-label>
<q-item-label caption>
Status: {{ download.status }}
<span v-if="download.completedAt">
- Completed: {{ formatDate(download.completedAt) }}
</span>
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useDownloadActivityStore } from '@ftx/ui'
const downloadStore = useDownloadActivityStore()
const allDownloads = computed(() => downloadStore.getAllDownloads)
function formatDate(date) {
return new Date(date).toLocaleString()
}
</script>Auto-Cleanup Completed Downloads
You can automatically remove completed or failed downloads after a delay:
<script setup>
import { watch } from 'vue'
import { useDownloadActivityStore } from '@ftx/ui'
const downloadStore = useDownloadActivityStore()
// Note: Completed downloads are automatically removed by the download helper
// after a delay (10 seconds for PDF/Print, 2 seconds for others)
// Error downloads remain visible until manually dismissed
</script>API Reference
State
| Property | Type | Description |
|---|---|---|
downloads | Object | Object mapping download IDs to download objects |
Download Object Structure
Each download object contains:
| Property | Type | Description |
|---|---|---|
id | string | Unique download identifier |
filename | string | Filename for the download (e.g., "export.xlsx") |
status | string | Status: 'downloading', 'completed', or 'error' |
progress | number | null | Progress value (0-1 for downloads with total, 0 for downloads without total), or null if not available |
format | number | null | Format: 0=PDF, 1=Excel, 2=CSV, 3=Print, or null |
tableTitle | string | null | Table title from which the export was generated |
error | string | null | Error message if download failed |
startedAt | Date | Timestamp when download was initiated |
completedAt | Date | null | Timestamp when download completed or failed |
Getters
getActiveDownloads
Returns an array of all active downloads (status is downloading).
Returns: Array<Download> - Array of active download objects
Example:
const downloadStore = useDownloadActivityStore()
const activeDownloads = downloadStore.getActiveDownloads
// Returns: [{ id: '...', filename: 'export.xlsx', status: 'in-progress', ... }, ...]getAllDownloads
Returns an array of all downloads, including completed and failed ones.
Returns: Array<Download> - Array of all download objects
Example:
const downloadStore = useDownloadActivityStore()
const allDownloads = downloadStore.getAllDownloads
// Returns: [{ id: '...', status: 'completed', ... }, { id: '...', status: 'failed', ... }, ...]getDownload(id)
Returns a specific download by ID.
Parameters:
id(string, required) - Download ID
Returns: Download | null - Download object or null if not found
Example:
const downloadStore = useDownloadActivityStore()
const download = downloadStore.getDownload('download_1234567890_abc123')
// Returns: { id: 'download_1234567890_abc123', filename: 'export.xlsx', ... } or nullhasActiveDownloads
Checks if there are any active downloads.
Returns: boolean - true if there are active downloads, false otherwise
Example:
const downloadStore = useDownloadActivityStore()
if (downloadStore.hasActiveDownloads) {
console.log('Downloads in progress')
}Actions
addDownload(download)
Adds or updates a download in the store. This is automatically called by the download helper, but can be used manually if needed.
Parameters:
download(Object, required) - Download information objectid(string, required) - Unique download identifierfilename(string, optional) - Filename for the downloadstatus(string, optional) - Status ('downloading','completed','error')progress(number, optional) - Progress value (0-1 for downloads with total, 0 for downloads without total)hasTotal(boolean, optional) - Whether the download has a total size for progress calculationformat(number, optional) - Format:0=PDF,1=Excel,2=CSV,3=PrinttableTitle(string, optional) - Table titleerror(string, optional) - Error messagestartedAt(Date, optional) - Start timestamp (defaults to current time)completedAt(Date, optional) - Completion timestamp
Example:
const downloadStore = useDownloadActivityStore()
downloadStore.addDownload({
id: 'my-download-123',
filename: 'report.xlsx',
status: 'pending',
format: 1, // Excel
tableTitle: 'Sales Report'
})updateDownload(id, status, updates)
Updates an existing download's status and optional additional properties.
Parameters:
id(string, required) - Download IDstatus(string, required) - New statusupdates(Object, optional) - Additional updates (progress, error, etc.)
Example:
const downloadStore = useDownloadActivityStore()
// Update progress
downloadStore.updateDownload('my-download-123', 'downloading', { progress: 0.5, hasTotal: true })
// Mark as completed
downloadStore.updateDownload('my-download-123', 'completed', { progress: 1 })
// Mark as error
downloadStore.updateDownload('my-download-123', 'error', { error: 'Network error' })removeDownload(id)
Removes a download from tracking.
Parameters:
id(string, required) - Download ID
Example:
const downloadStore = useDownloadActivityStore()
downloadStore.removeDownload('my-download-123')clearCompletedDownloads()
Removes all completed and error downloads from tracking.
Example:
const downloadStore = useDownloadActivityStore()
downloadStore.clearCompletedDownloads()clearAllDownloads()
Removes all downloads from tracking.
Example:
const downloadStore = useDownloadActivityStore()
downloadStore.clearAllDownloads()Integration with FtxGrid
The download activity store is automatically integrated with FtxGrid export functionality. When users export data from a grid:
- The download is automatically added to the store
- Progress is tracked during the download
- Status updates are emitted automatically
- Your UI components can react to these changes
No additional configuration is needed - just subscribe to the store in your download activity component.
Best Practices
- Display Active Downloads Only: Use
getActiveDownloadsto show only downloading downloads in your UI - Auto-Cleanup: Completed downloads are automatically removed after a delay (10s for PDF/Print, 2s for others)
- Error Handling: Always display error messages from error downloads - they remain visible until manually dismissed
- Progress Indicators: Show progress bars for downloading downloads when available (progress is 0-1, not 0-100)
- User Actions: Provide a way for users to dismiss downloads manually
Example: Complete Sidebar Component
Here's a complete example of a sidebar download activity component:
<template>
<q-list bordered class="rounded-borders">
<q-item-label header>
<div class="row items-center justify-between">
<span>Download Activity</span>
<q-btn
v-if="hasActiveDownloads || hasCompletedDownloads"
flat
dense
size="sm"
label="Clear"
@click="clearCompleted"
/>
</div>
</q-item-label>
<!-- No downloads message -->
<q-item v-if="!hasActiveDownloads && !hasCompletedDownloads">
<q-item-section>
<q-item-label class="text-grey-6">No downloads</q-item-label>
</q-item-section>
</q-item>
<!-- Active downloads -->
<q-item
v-for="download in activeDownloads"
:key="download.id"
class="download-item q-mb-xs"
>
<q-item-section avatar>
<q-spinner
v-if="download.status === 'downloading'"
color="primary"
size="24px"
/>
</q-item-section>
<q-item-section>
<q-item-label>{{ download.filename }}</q-item-label>
<q-item-label caption>{{ download.tableTitle }}</q-item-label>
<q-linear-progress
v-if="download.status === 'downloading' && download.progress !== null"
:value="download.progress"
color="primary"
class="q-mt-xs"
/>
</q-item-section>
<q-item-section side>
<q-btn
flat
round
dense
icon="close"
size="sm"
@click="removeDownload(download.id)"
/>
</q-item-section>
</q-item>
<!-- Completed/failed downloads -->
<q-separator v-if="hasActiveDownloads && hasCompletedDownloads" />
<q-item
v-for="download in completedDownloads"
:key="download.id"
class="download-item"
>
<q-item-section avatar>
<q-icon
:name="download.status === 'completed' ? 'check_circle' : 'error'"
:color="download.status === 'completed' ? 'positive' : 'negative'"
size="24px"
/>
</q-item-section>
<q-item-section>
<q-item-label>{{ download.filename }}</q-item-label>
<q-item-label caption>
<span v-if="download.error" class="text-negative">{{ download.error }}</span>
<span v-else>Download completed</span>
</q-item-label>
</q-item-section>
<q-item-section side>
<q-btn
flat
round
dense
icon="close"
size="sm"
@click="removeDownload(download.id)"
/>
</q-item-section>
</q-item>
</q-list>
</template>
<script setup>
import { computed } from 'vue'
import { useDownloadActivityStore } from '@ftx/ui'
const downloadStore = useDownloadActivityStore()
const activeDownloads = computed(() => downloadStore.getActiveDownloads)
const hasActiveDownloads = computed(() => downloadStore.hasActiveDownloads)
const completedDownloads = computed(() => {
return downloadStore.getAllDownloads.filter(
(d) => d.status === 'completed' || d.status === 'error'
)
})
const hasCompletedDownloads = computed(() => completedDownloads.value.length > 0)
function removeDownload(id) {
downloadStore.removeDownload(id)
}
function clearCompleted() {
downloadStore.clearCompletedDownloads()
}
</script>
<style scoped>
.download-item {
padding: 8px 16px;
}
</style>Notes
- The store automatically handles download lifecycle events
- Downloads are tracked automatically when using
FtxGridexport/print features - Progress tracking depends on your API's support for download progress events
- Progress values are 0-1 (not 0-100) - divide by 100 only if your progress component expects percentage
- Completed downloads are automatically removed after a delay (10 seconds for PDF/Print, 2 seconds for others)
- Error downloads remain visible until manually dismissed
- The store uses Pinia's reactive system, so components automatically update when downloads change
- Hot Module Replacement (HMR) is supported during development