Add Delete Files action

Closes #92
This commit is contained in:
Nils Maier 2019-09-12 11:19:41 +02:00
parent dcf9603da8
commit 58c7955c64
7 changed files with 136 additions and 7 deletions

View File

@ -10,7 +10,6 @@ Planned for later.
* Soft errors and retry logic
* Big caveat: When the server still responds, like 50x errors which would be recoverable, we actually have no way of knowing it did in respond in such a way. See P4 - Handle Errors remarks.
* Delete files (well, as far as the browser allows)
* Inter-addon API (basic)
* Add downloads
* vtable perf: cache column widths

View File

@ -207,6 +207,22 @@
"description": "button text",
"message": "Delete"
},
"deletefiles": {
"description": "menu action",
"message": "Delete Files"
},
"deletefiles_button": {
"description": "button text",
"message": "Delete"
},
"deletefiles_text": {
"description": "messagebox text",
"message": "Are you sure you want to delete the following files?"
},
"deletefiles_title": {
"description": "messagebox title",
"message": "Delete Files"
},
"description": {
"description": "Description (keep it short); e.g. the description column in select",
"message": "Description"
@ -529,10 +545,6 @@
"description": "Status text; manager",
"message": "Paused"
},
"pref_sounds": {
"description": "checkbox text",
"message": "Play sounds"
},
"pref_add_paused": {
"description": "Preferences/General",
"message": "Add new downloads paused, instead of starting them immediately"
@ -585,6 +597,10 @@
"description": "Preferences/General",
"message": "Show URLs instead of Names"
},
"pref_sounds": {
"description": "checkbox text",
"message": "Play sounds"
},
"pref_text_links": {
"description": "Preferences/General",
"message": "Try to find links in the website text (slower)"

View File

@ -81,6 +81,7 @@ interface Downloads {
search(query: DownloadsQuery): Promise<any[]>;
getFileIcon(id: number, options?: any): Promise<string>;
setShelfEnabled(state: boolean): void;
removeFile(manId: number): Promise<void>;
onCreated: ExtensionListener;
onChanged: ExtensionListener;
onErased: ExtensionListener;

View File

@ -511,4 +511,19 @@ body > * {
height: 100%;
width: 100%;
background: var(--done-color);
}
.deletefiles-list {
padding-left: 1ex;
padding-right: 1.5ex;
border: 1px solid lightgray;
border-radius: 6px;
background-color: rgba(128,128,128,0.1);
max-height: 8em;
overflow-y: auto;
}
.deletefiles-list > li {
list-style-type: none;
padding: 0;
margin: 0;
}

View File

@ -75,6 +75,7 @@
<ul id="table-context" class="table-context">
<li id="ctx-open-file" data-key="Enter" data-i18n="open-file" data-icon="icon-file">Open File</li>
<li id="ctx-open-directory" data-key="ACCEL-Enter" data-i18n="open-directory" data-icon="icon-folder">Open Directory</li>
<li id="ctx-delete-files" data-key="ACCEL-Delete" data-i18n="deletefiles" data-icon="icon-delete"></li>
<li>-</li>
<li id="ctx-resume" data-key="ACCEL-KeyR" data-i18n="resume-download" data-icon="icon-go">Resume</li>
<li id="ctx-pause" data-key="ACCEL-KeyP" data-i18n="pause-download" data-icon="icon-pause">Pause</li>
@ -125,6 +126,12 @@
</p>
</template>
<template id="deletefiles-template">
<h1 class="deletefiles-title" data-i18n="deletefiles_title"></h1>
<p class="deletefiles-text" data-i18n="deletefiles_text"></p>
<ul class="deletefiles-list"></ul>
</template>
<template id="menufilter-template">
<ul>
<li id="ctx-menufilter-seperator">-</li>

View File

@ -7,7 +7,7 @@ import { Prefs } from "../../lib/prefs";
import { Keys } from "../keys";
import { $ } from "../winutil";
export default class RemovalModalDialog extends ModalDialog {
export class RemovalModalDialog extends ModalDialog {
private readonly text: string;
private readonly pref: string;
@ -68,3 +68,57 @@ export default class RemovalModalDialog extends ModalDialog {
this.focusDefault();
}
}
export class DeleteFilesDialog extends ModalDialog {
private readonly paths: string[];
constructor(paths: string[]) {
super();
this.paths = paths;
}
async getContent() {
const content = $<HTMLTemplateElement>("#deletefiles-template").
content.cloneNode(true) as DocumentFragment;
await localize(content);
const list = $(".deletefiles-list", content);
for (const path of this.paths) {
const li = document.createElement("li");
li.textContent = path;
list.appendChild(li);
}
return content;
}
get buttons() {
return [
{
title: _("deletefiles_button"),
value: "ok",
default: true,
dismiss: false,
},
{
title: _("cancel"),
value: "cancel",
default: false,
dismiss: true,
}
];
}
async show() {
Keys.suppressed = true;
try {
return await super.show();
}
finally {
Keys.suppressed = false;
}
}
shown() {
this.focusDefault();
}
}

View File

@ -26,7 +26,7 @@ import {
MenuFilter
} from "./itemfilters";
import { FilteredCollection } from "./itemfilters";
import RemovalModalDialog from "./removaldlg";
import { RemovalModalDialog, DeleteFilesDialog } from "./removaldlg";
import { Stats } from "./stats";
import PORT from "./port";
import { DownloadState, StateTexts, StateClasses, StateIcons } from "./state";
@ -404,6 +404,8 @@ export class DownloadTable extends VirtualTable {
private readonly openDirectoryAction: Broadcaster;
private readonly deleteFilesAction: Broadcaster;
private readonly moveTopAction: Broadcaster;
private readonly moveUpAction: Broadcaster;
@ -579,6 +581,9 @@ export class DownloadTable extends VirtualTable {
this.openDirectoryAction = new Broadcaster("ctx-open-directory");
this.openDirectoryAction.onaction = this.openDirectory.bind(this);
this.deleteFilesAction = new Broadcaster("ctx-delete-files");
this.deleteFilesAction.onaction = this.deleteFiles.bind(this);
const moveAction = (method: string) => {
if (this.selection.empty) {
return;
@ -610,6 +615,7 @@ export class DownloadTable extends VirtualTable {
this.moveBottomAction,
this.openFileAction,
this.openDirectoryAction,
this.deleteFilesAction,
]);
this.on(
@ -782,6 +788,10 @@ export class DownloadTable extends VirtualTable {
this.cancelAction.disabled = true;
}
if (!(states & DownloadState.DONE)) {
this.deleteFilesAction.disabled = true;
}
const item = this.focusRow >= 0 ?
this.downloads.filtered[this.focusRow] :
null;
@ -860,6 +870,33 @@ export class DownloadTable extends VirtualTable {
}
}
async deleteFiles() {
const items = [];
for (const rowid of this.selection) {
const item = this.downloads.filtered[rowid];
if (item.state === DownloadState.DONE && item.manId) {
items.push(item);
}
}
if (!items.length) {
return;
}
const sids = items.map(i => i.sessionId);
const paths = items.map(i => i.destFull);
await new DeleteFilesDialog(paths).show();
await Promise.all(items.map(async item => {
try {
if (item.manId && item.state === DownloadState.DONE) {
await downloads.removeFile(item.manId);
}
}
catch {
// ignored
}
}));
this.removeDownloadsInternal(sids);
}
removeDownloadsInternal(sids?: number[]) {
if (!sids) {
sids = [];