Retain interrupted download progress cards and add in-place retry action
- Keep failed or interrupted downloads in active downloads queue instead of dismissing immediately - Present interrupted state with error tooltip, distinct status chip, and progress percentage - Add in-place Retry download play action that resumes downloading from existing partial files - Support dismissing interrupted downloads via cancel button - Update CSS styling and unit tests for download action handlers and progress states
This commit is contained in:
@@ -62,6 +62,7 @@ function createDownloadDialog(queueList, calls) {
|
||||
cancelDownload: id => calls.push(['cancel', id]),
|
||||
pauseDownload: id => calls.push(['pause', id]),
|
||||
resumeDownload: id => calls.push(['resume', id]),
|
||||
retryDownload: id => calls.push(['retry', id]),
|
||||
openContainingFolder: context => calls.push(['folder', context.widget_name]),
|
||||
switchToDownloadWorkflow: context => calls.push(['workflow', context.widget_name]),
|
||||
setQueueDownloadsTab: () => {},
|
||||
@@ -83,6 +84,7 @@ test('queue download controls trigger each action once and keep more as click-on
|
||||
'mr-download-queue-cancel',
|
||||
'mr-download-queue-pause',
|
||||
'mr-download-queue-resume',
|
||||
'mr-download-queue-retry',
|
||||
'mr-download-queue-open-folder',
|
||||
'mr-download-queue-switch-workflow',
|
||||
'mr-download-queue-more',
|
||||
@@ -103,12 +105,14 @@ test('queue download controls trigger each action once and keep more as click-on
|
||||
dispatchPointerAndClick(window, buttons[2]);
|
||||
dispatchPointerAndClick(window, buttons[3]);
|
||||
dispatchPointerAndClick(window, buttons[4]);
|
||||
buttons[5].dispatchEvent(new window.Event('click', { bubbles: true, cancelable: true }));
|
||||
dispatchPointerAndClick(window, buttons[5]);
|
||||
buttons[6].dispatchEvent(new window.Event('click', { bubbles: true, cancelable: true }));
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
['cancel', 'download-1'],
|
||||
['pause', 'download-1'],
|
||||
['resume', 'download-1'],
|
||||
['retry', 'download-1'],
|
||||
['folder', 'test-model'],
|
||||
['workflow', 'test-model'],
|
||||
]);
|
||||
@@ -124,6 +128,7 @@ test('progress download controls support pending buttons and fallback download i
|
||||
'cancel-download-btn-pending',
|
||||
'pause-download-btn',
|
||||
'resume-download-btn',
|
||||
'retry-download-btn',
|
||||
'mr-download-queue-open-folder',
|
||||
'mr-download-queue-switch-workflow',
|
||||
'mr-download-queue-more',
|
||||
@@ -145,16 +150,19 @@ test('progress download controls support pending buttons and fallback download i
|
||||
buttons[3].dataset.downloadId = '';
|
||||
buttons[4].dataset.downloadId = '';
|
||||
buttons[5].dataset.downloadId = '';
|
||||
buttons[6].dataset.downloadId = '';
|
||||
dispatchPointerAndClick(window, buttons[0]);
|
||||
dispatchPointerAndClick(window, buttons[1]);
|
||||
dispatchPointerAndClick(window, buttons[2]);
|
||||
dispatchPointerAndClick(window, buttons[3]);
|
||||
dispatchPointerAndClick(window, buttons[4]);
|
||||
dispatchPointerAndClick(window, buttons[5]);
|
||||
|
||||
assert.deepEqual(calls, [
|
||||
['cancel', 'fallback-download'],
|
||||
['pause', 'fallback-download'],
|
||||
['resume', 'fallback-download'],
|
||||
['retry', 'fallback-download'],
|
||||
['folder', 'test-model'],
|
||||
['workflow', 'test-model'],
|
||||
]);
|
||||
|
||||
@@ -13,6 +13,8 @@ test('download progress labels cover active, paused, checking, and terminal stat
|
||||
assert.equal(context.getDownloadProgressStatusLabel('downloading', 42.5, true), 'Finalizing');
|
||||
assert.equal(context.getDownloadProgressStatusLabel('starting'), 'Starting');
|
||||
assert.equal(context.getDownloadProgressStatusLabel('paused'), 'Paused');
|
||||
assert.equal(context.getDownloadProgressStatusLabel('error'), 'Interrupted');
|
||||
assert.equal(context.getDownloadProgressStatusLabel('refresh_error'), 'Interrupted');
|
||||
assert.equal(context.getDownloadProgressStatusLabel('completed_checking'), 'Checking');
|
||||
assert.equal(context.getDownloadProgressStatusLabel('cancelled'), 'cancelled');
|
||||
assert.equal(context.getDownloadProgressStatusLabel(''), 'Download');
|
||||
@@ -26,6 +28,10 @@ test('download progress status distinguishes active and terminal states', () =>
|
||||
assert.equal(isActive('paused'), true);
|
||||
assert.equal(isActive('cancelling'), false);
|
||||
assert.equal(isActive('completed'), false);
|
||||
assert.equal(isActive('error', true), true);
|
||||
assert.equal(isActive('error', false), false);
|
||||
assert.equal(isActive('refresh_error', true), true);
|
||||
assert.equal(isActive('refresh_error', false), false);
|
||||
assert.equal(isActive('custom', true), true);
|
||||
assert.equal(isActive('custom', false), false);
|
||||
});
|
||||
|
||||
@@ -769,6 +769,29 @@
|
||||
border-radius: var(--mr-missing-chip-radius);
|
||||
}
|
||||
|
||||
.mr-download-queue-item.mr-download-queue-item-error {
|
||||
border-color: rgba(239, 68, 68, 0.35);
|
||||
}
|
||||
|
||||
.mr-download-queue-status.mr-download-queue-status-error {
|
||||
color: var(--mr-color-danger-bright, #ff4d4f);
|
||||
background: var(--mr-badge-error-bg, rgba(239, 68, 68, 0.16));
|
||||
border-color: var(--mr-badge-error-border, rgba(239, 68, 68, 0.34));
|
||||
}
|
||||
|
||||
.mr-download-queue-progress .mr-progress-fill.mr-progress-fill-error {
|
||||
background: var(--mr-color-danger-bright, #ff4d4f);
|
||||
box-shadow: 0 0 10px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.mr-download-queue-stat.mr-download-queue-stat-error {
|
||||
max-width: 220px;
|
||||
overflow: hidden;
|
||||
color: var(--mr-color-danger-bright, #ff4d4f);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mr-download-history-toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -18,12 +18,13 @@ export const downloadProgressMethods = {
|
||||
}
|
||||
if (status === 'starting') return 'Starting';
|
||||
if (status === 'paused') return 'Paused';
|
||||
if (status === 'error' || status === 'refresh_error') return 'Interrupted';
|
||||
if (status === 'completed_checking') return 'Checking';
|
||||
return String(status || '').replace(/_/g, ' ') || 'Download';
|
||||
},
|
||||
|
||||
isDownloadProgressStatus(status = '', isActive = false) {
|
||||
const terminalStatuses = new Set(['cancelling', 'cancelled', 'error', 'refresh_error', 'completed_checking', 'completed']);
|
||||
const terminalStatuses = new Set(['cancelling', 'cancelled', 'completed_checking', 'completed']);
|
||||
return status === 'starting'
|
||||
|| status === 'downloading'
|
||||
|| status === 'paused'
|
||||
|
||||
@@ -1160,6 +1160,7 @@ export const queueMethods = {
|
||||
cancel: '.mr-download-queue-cancel',
|
||||
pause: '.mr-download-queue-pause',
|
||||
resume: '.mr-download-queue-resume',
|
||||
retry: '.mr-download-queue-retry',
|
||||
openFolder: '.mr-download-queue-open-folder',
|
||||
switchWorkflow: '.mr-download-queue-switch-workflow',
|
||||
more: '.mr-download-queue-more',
|
||||
@@ -1169,6 +1170,7 @@ export const queueMethods = {
|
||||
onCancel: downloadId => this.cancelDownload(downloadId),
|
||||
onPause: downloadId => this.pauseDownload(downloadId),
|
||||
onResume: downloadId => this.resumeDownload(downloadId),
|
||||
onRetry: downloadId => this.retryDownload?.(downloadId),
|
||||
onOpenFolder: context => this.openContainingFolder?.(context),
|
||||
onSwitchWorkflow: context => this.switchToDownloadWorkflow?.(context),
|
||||
moreBinding: 'click',
|
||||
@@ -1229,9 +1231,10 @@ export const queueMethods = {
|
||||
const backend = String(progress.download_backend || info.downloadBackend || '').toLowerCase();
|
||||
const isAria2 = backend === 'aria2';
|
||||
const isPaused = status === 'paused';
|
||||
const isError = status === 'error' || status === 'refresh_error';
|
||||
const statusLabel = status === 'downloading'
|
||||
? (presentation.isFinalizing ? 'Finalizing' : '')
|
||||
: (status === 'starting' ? 'Starting' : (isPaused ? 'Paused' : status.replace(/_/g, ' ')));
|
||||
: (status === 'starting' ? 'Starting' : (isPaused ? 'Paused' : (isError ? 'Interrupted' : status.replace(/_/g, ' '))));
|
||||
const sizeTextBase = presentation.isFinalizing && logicalTotal
|
||||
? `Finalizing file: ${logicalDownloaded} / ${logicalTotal}`
|
||||
: (presentation.totalSize ? `${downloaded} / ${total}` : downloaded);
|
||||
@@ -1260,21 +1263,27 @@ export const queueMethods = {
|
||||
`<button type="button" class="mr-download-queue-action ${className}" data-download-id="${safeDownloadId}" data-tooltip="${label}" aria-label="${label}"${disabled ? ' disabled' : ''}>${renderIcon(iconName, 'mr-download-queue-action-icon')}</button>`
|
||||
);
|
||||
|
||||
html += `<div class="mr-queue-item mr-download-queue-item" data-download-id="${this.escapeHtml(downloadId)}"${contextData}>`;
|
||||
html += `<div class="mr-queue-item mr-download-queue-item${isError ? ' mr-download-queue-item-error' : ''}" data-download-id="${this.escapeHtml(downloadId)}"${contextData}>`;
|
||||
html += `<div class="mr-download-queue-header">`;
|
||||
html += `<div class="mr-download-queue-heading">`;
|
||||
html += `<span class="mr-download-queue-filename" data-tooltip="${this.escapeHtml(filename)}">${this.escapeHtml(filename)}</span>`;
|
||||
if (statusLabel) {
|
||||
html += `<span class="mr-download-queue-status">${this.escapeHtml(statusLabel)}</span>`;
|
||||
const errorMessage = progress.error || info.statusSnapshot?.message || '';
|
||||
const errorTooltip = isError && errorMessage ? ` data-tooltip="${this.escapeHtml(errorMessage)}"` : '';
|
||||
html += `<span class="mr-download-queue-status${isError ? ' mr-download-queue-status-error' : ''}"${errorTooltip}>${this.escapeHtml(statusLabel)}</span>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
html += `<div class="mr-download-queue-actions" role="group" aria-label="Download actions">`;
|
||||
html += actionButton(
|
||||
isPaused ? 'mr-download-queue-resume' : 'mr-download-queue-pause',
|
||||
isPaused ? 'Resume download' : 'Pause download',
|
||||
isPaused ? 'play' : 'pause',
|
||||
!isAria2
|
||||
);
|
||||
if (isError) {
|
||||
html += actionButton('mr-download-queue-retry', 'Retry download', 'play');
|
||||
} else {
|
||||
html += actionButton(
|
||||
isPaused ? 'mr-download-queue-resume' : 'mr-download-queue-pause',
|
||||
isPaused ? 'Resume download' : 'Pause download',
|
||||
isPaused ? 'play' : 'pause',
|
||||
!isAria2
|
||||
);
|
||||
}
|
||||
html += actionButton('mr-download-queue-cancel', 'Cancel download', 'x');
|
||||
html += actionButton('mr-download-queue-open-folder', 'Open download folder', 'folderOpen', !hasFolderAction);
|
||||
if (showWorkflowAction) {
|
||||
@@ -1284,20 +1293,23 @@ export const queueMethods = {
|
||||
html += `</div>`;
|
||||
html += `</div>`;
|
||||
html += `<div class="mr-download-queue-progress">`;
|
||||
html += `<div class="mr-progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${percent}" aria-label="Download progress"><div class="mr-progress-fill" style="width: ${percent}%;"></div></div>`;
|
||||
html += `<div class="mr-progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${percent}" aria-label="Download progress"><div class="mr-progress-fill${isError ? ' mr-progress-fill-error' : ''}" style="width: ${percent}%;"></div></div>`;
|
||||
html += `<div class="mr-download-queue-stats">`;
|
||||
html += `<span class="mr-download-queue-size">${this.escapeHtml(sizeText)}</span>`;
|
||||
html += `<span class="mr-download-queue-stat-group">`;
|
||||
if (speedText) {
|
||||
if (speedText && !isError) {
|
||||
html += `<span class="mr-download-queue-stat">${renderIcon('download', 'mr-download-queue-stat-icon')}<span>${this.escapeHtml(speedText)}</span></span>`;
|
||||
}
|
||||
if (etaText) {
|
||||
if (etaText && !isError) {
|
||||
html += `<span class="mr-download-queue-stat"><span>${this.escapeHtml(etaText)}</span></span>`;
|
||||
}
|
||||
if (connections > 0) {
|
||||
if (connections > 0 && !isError) {
|
||||
html += `<span class="mr-download-queue-stat" data-tooltip="Active download connections">${renderIcon('network', 'mr-download-queue-stat-icon')}<span>${connections}</span></span>`;
|
||||
}
|
||||
if (!speedText && !etaText) {
|
||||
if (isError) {
|
||||
const errorMessage = progress.error || info.statusSnapshot?.message || 'Download interrupted';
|
||||
html += `<span class="mr-download-queue-stat mr-download-queue-stat-error" data-tooltip="${this.escapeHtml(errorMessage)}"><span>${this.escapeHtml(errorMessage)}</span></span>`;
|
||||
} else if (!speedText && !etaText) {
|
||||
html += `<span class="mr-download-queue-stat mr-download-queue-stat-muted">${this.escapeHtml(statusLabel)}</span>`;
|
||||
}
|
||||
html += `</span>`;
|
||||
|
||||
@@ -1132,6 +1132,7 @@ export const resolveDownloadMethods = {
|
||||
cancel: '.cancel-download-btn, .cancel-download-btn-pending, .mr-download-queue-cancel',
|
||||
pause: '.pause-download-btn, .mr-download-queue-pause',
|
||||
resume: '.resume-download-btn, .mr-download-queue-resume',
|
||||
retry: '.retry-download-btn, .mr-download-queue-retry',
|
||||
openFolder: '.mr-download-queue-open-folder',
|
||||
switchWorkflow: '.mr-download-queue-switch-workflow',
|
||||
more: '.mr-download-queue-more',
|
||||
@@ -1140,6 +1141,7 @@ export const resolveDownloadMethods = {
|
||||
onCancel: targetDownloadId => this.cancelDownload(targetDownloadId),
|
||||
onPause: targetDownloadId => this.pauseDownload(targetDownloadId),
|
||||
onResume: targetDownloadId => this.resumeDownload(targetDownloadId),
|
||||
onRetry: targetDownloadId => this.retryDownload?.(targetDownloadId),
|
||||
onOpenFolder: context => this.openContainingFolder?.(context),
|
||||
onSwitchWorkflow: context => this.switchToDownloadWorkflow?.(context),
|
||||
onMore: (event, button) => {
|
||||
@@ -1943,6 +1945,8 @@ export const resolveDownloadMethods = {
|
||||
downloadDirectory: '',
|
||||
baseDirectory,
|
||||
sourceUrl: url,
|
||||
pathMetadata,
|
||||
downloadMetadata,
|
||||
workflowKey,
|
||||
workflowRouteKey,
|
||||
workflowLabel,
|
||||
@@ -1995,6 +1999,17 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
|
||||
const downloadId = data.download_id;
|
||||
if (missing) {
|
||||
for (const [existingId, existingInfo] of Object.entries(this.activeDownloads || {})) {
|
||||
if (existingInfo?.missing === missing) {
|
||||
const existingStatus = String(existingInfo.lastStatus || existingInfo.lastProgress?.status || '').toLowerCase();
|
||||
if (existingStatus === 'error' || existingStatus === 'refresh_error') {
|
||||
delete this.activeDownloads[existingId];
|
||||
this.removeActiveDownloadRecovery?.(existingId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.activeDownloads[downloadId] = {
|
||||
missing,
|
||||
progressDiv,
|
||||
@@ -2006,6 +2021,8 @@ export const resolveDownloadMethods = {
|
||||
downloadDirectory: data.directory || '',
|
||||
baseDirectory,
|
||||
sourceUrl: url,
|
||||
pathMetadata,
|
||||
downloadMetadata,
|
||||
workflowKey,
|
||||
workflowRouteKey,
|
||||
workflowLabel,
|
||||
@@ -2049,6 +2066,8 @@ export const resolveDownloadMethods = {
|
||||
downloadDirectory: '',
|
||||
baseDirectory,
|
||||
sourceUrl: url,
|
||||
pathMetadata,
|
||||
downloadMetadata,
|
||||
workflowKey,
|
||||
workflowRouteKey,
|
||||
workflowLabel,
|
||||
@@ -2253,11 +2272,13 @@ export const resolveDownloadMethods = {
|
||||
status: 'error',
|
||||
message: progress.error || 'Download failed',
|
||||
type: 'error',
|
||||
isActive: false
|
||||
isActive: true
|
||||
});
|
||||
info.lastStatus = 'error';
|
||||
info.lastProgress = progress;
|
||||
info.statusSnapshot = errorSnapshot;
|
||||
this.renderDownloadSnapshot(downloadId, errorSnapshot, { progressDiv, downloadBtn });
|
||||
delete this.activeDownloads[downloadId];
|
||||
this.removeActiveDownloadRecovery?.(downloadId);
|
||||
this.persistActiveDownloadRecovery?.(downloadId, this.activeDownloads[downloadId]);
|
||||
this.updateDownloadAllButtonState();
|
||||
this.updateQueuePanel?.();
|
||||
this.refreshLocalMatchesUiForMissing?.(missing);
|
||||
@@ -2279,18 +2300,20 @@ export const resolveDownloadMethods = {
|
||||
const snapshot = this.rememberDownloadUiState(
|
||||
downloadId,
|
||||
info,
|
||||
{ ...(info.lastProgress || {}), status: 'error' },
|
||||
{ ...(info.lastProgress || {}), status: 'error', error: error.message },
|
||||
{
|
||||
status: 'error',
|
||||
message: 'Connection lost - download may have failed',
|
||||
type: 'error',
|
||||
isActive: false
|
||||
isActive: true
|
||||
}
|
||||
);
|
||||
info.lastStatus = 'error';
|
||||
info.lastProgress = { ...(info.lastProgress || {}), status: 'error', error: error.message };
|
||||
info.statusSnapshot = snapshot;
|
||||
this.renderDownloadSnapshot(downloadId, snapshot);
|
||||
this.persistActiveDownloadRecovery?.(downloadId, this.activeDownloads[downloadId]);
|
||||
}
|
||||
delete this.activeDownloads[downloadId];
|
||||
this.removeActiveDownloadRecovery?.(downloadId);
|
||||
this.updateDownloadAllButtonState();
|
||||
this.updateQueuePanel?.();
|
||||
}
|
||||
@@ -2314,6 +2337,40 @@ export const resolveDownloadMethods = {
|
||||
*/
|
||||
cancelDownload(downloadId) {
|
||||
const info = this.activeDownloads[downloadId];
|
||||
const currentStatus = String(
|
||||
info?.pendingDownloadStatus
|
||||
|| info?.lastStatus
|
||||
|| info?.lastProgress?.status
|
||||
|| ''
|
||||
).toLowerCase();
|
||||
|
||||
if (currentStatus === 'error' || currentStatus === 'refresh_error') {
|
||||
delete this.activeDownloads[downloadId];
|
||||
this.removeActiveDownloadRecovery?.(downloadId);
|
||||
if (info) {
|
||||
const elements = this.resolveDownloadUiElements?.(info) || {};
|
||||
const cancelledSnapshot = this.rememberDownloadUiState(
|
||||
downloadId,
|
||||
info,
|
||||
{ ...(info.lastProgress || {}), status: 'cancelled' },
|
||||
{
|
||||
status: 'cancelled',
|
||||
message: 'Download dismissed',
|
||||
type: 'info',
|
||||
isActive: false
|
||||
}
|
||||
);
|
||||
this.renderDownloadSnapshot?.(downloadId, cancelledSnapshot, elements);
|
||||
this.refreshLocalMatchesUiForMissing?.(info.missing);
|
||||
}
|
||||
this.updateDownloadAllButtonState?.();
|
||||
this.updateQueuePanel?.();
|
||||
this.fetchJson(`/model_resolver/cancel/${downloadId}`, {
|
||||
method: 'POST'
|
||||
}, 'Cancel download').catch(() => {});
|
||||
return;
|
||||
}
|
||||
|
||||
if (info) {
|
||||
this.setPendingDownloadStatus(info, 'cancelling', 30000);
|
||||
const snapshot = this.rememberDownloadUiState(
|
||||
@@ -2432,8 +2489,62 @@ export const resolveDownloadMethods = {
|
||||
});
|
||||
},
|
||||
|
||||
async retryDownload(downloadId) {
|
||||
const info = this.activeDownloads?.[downloadId];
|
||||
if (!info) return;
|
||||
|
||||
const missing = info.missing;
|
||||
const sourceUrl = info.sourceUrl || missing?.download_source?.url || '';
|
||||
const filename = info.filename || missing?.download_source?.filename || '';
|
||||
const category = info.category || missing?.category || 'checkpoints';
|
||||
const subfolder = info.subfolder || '';
|
||||
const baseDirectory = info.baseDirectory || '';
|
||||
const pathMetadata = info.pathMetadata || (missing && missing.download_source
|
||||
? this.getDownloadPathMetadata?.(missing, missing.download_source)
|
||||
: null);
|
||||
const downloadMetadata = info.downloadMetadata || (missing && missing.download_source
|
||||
? this.getDownloadMetadata?.(missing, missing.download_source, {
|
||||
filename,
|
||||
category,
|
||||
url: sourceUrl,
|
||||
pathMetadata
|
||||
})
|
||||
: null);
|
||||
|
||||
if (!sourceUrl) {
|
||||
this.showNotification('Cannot retry download: missing download URL', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
delete this.activeDownloads[downloadId];
|
||||
this.removeActiveDownloadRecovery?.(downloadId);
|
||||
|
||||
const elements = this.resolveDownloadUiElements?.(info) || {};
|
||||
const btn = elements.downloadBtn || info.downloadBtn || null;
|
||||
|
||||
await this.executeDownloadRequest(missing, {
|
||||
url: sourceUrl,
|
||||
filename,
|
||||
category,
|
||||
subfolder,
|
||||
baseDirectory,
|
||||
pathMetadata,
|
||||
downloadMetadata,
|
||||
btn
|
||||
});
|
||||
},
|
||||
|
||||
resumeDownload(downloadId) {
|
||||
const info = this.activeDownloads[downloadId];
|
||||
const currentStatus = String(
|
||||
info?.pendingDownloadStatus
|
||||
|| info?.lastStatus
|
||||
|| info?.lastProgress?.status
|
||||
|| ''
|
||||
).toLowerCase();
|
||||
if (currentStatus === 'error' || currentStatus === 'refresh_error') {
|
||||
return this.retryDownload(downloadId);
|
||||
}
|
||||
const previousProgress = info?.lastProgress ? { ...info.lastProgress } : null;
|
||||
if (info) {
|
||||
this.setPendingDownloadStatus(info, 'downloading');
|
||||
|
||||
@@ -27,6 +27,7 @@ export function bindDownloadActionHandlers(root, {
|
||||
onCancel = null,
|
||||
onPause = null,
|
||||
onResume = null,
|
||||
onRetry = null,
|
||||
onOpenFolder = null,
|
||||
onSwitchWorkflow = null,
|
||||
onMore = null,
|
||||
@@ -47,6 +48,7 @@ export function bindDownloadActionHandlers(root, {
|
||||
bindIdAction(selectors.cancel, onCancel);
|
||||
bindIdAction(selectors.pause, onPause);
|
||||
bindIdAction(selectors.resume, onResume);
|
||||
bindIdAction(selectors.retry, onRetry);
|
||||
|
||||
bindActionButtons(root, selectors.openFolder, (button) => {
|
||||
const context = resolveContext(button);
|
||||
|
||||
Reference in New Issue
Block a user