Skip to content

Commit a448e86

Browse files
committed
Added reordering of download queue (#233)
1 parent 8f703d7 commit a448e86

File tree

8 files changed

+107
-9
lines changed

8 files changed

+107
-9
lines changed

backend/base/custom_exceptions.py

+5
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,11 @@ def api_response(self):
328328
}
329329

330330

331+
class DownloadUnmovable(CustomException):
332+
"""The position of the download in the queue can not be changed"""
333+
api_response = {'error': 'DownloadUnmovable', 'result': {}, 'code': 400}
334+
335+
331336
class ExternalClientNotFound(CustomException):
332337
"""External client with given ID not found"""
333338
api_response = {

backend/features/download_queue.py

+32-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
from typing_extensions import assert_never
1111

1212
from backend.base.custom_exceptions import (DownloadLimitReached,
13-
DownloadNotFound, FailedGCPage,
14-
IssueNotFound, LinkBroken)
13+
DownloadNotFound,
14+
DownloadUnmovable, FailedGCPage,
15+
InvalidKeyValue, IssueNotFound,
16+
LinkBroken)
1517
from backend.base.definitions import (BlocklistReason, Constants,
1618
Download, DownloadSource,
1719
DownloadState, ExternalDownload,
@@ -199,6 +201,34 @@ def _process_queue(self) -> None:
199201

200202
return
201203

204+
def set_queue_location(
205+
self,
206+
download_id: int,
207+
index: int
208+
) -> None:
209+
"""Set the location of a download in the queue.
210+
211+
Args:
212+
download_id (int): The ID of the download to move.
213+
214+
index (int): The new index of the download.
215+
216+
Raises:
217+
DownloadNotFound: The ID doesn't map to any download in the queue.
218+
DownloadUnmovable: The download is not allowed to be moved.
219+
InvalidKeyValue: The index is out of bounds.
220+
"""
221+
download = self.get_one(download_id)
222+
if download.state != DownloadState.QUEUED_STATE:
223+
raise DownloadUnmovable
224+
225+
if index < 0 or index >= len(self.queue):
226+
raise InvalidKeyValue('index', index)
227+
228+
self.queue.remove(download)
229+
self.queue.insert(index, download)
230+
return
231+
202232
def __prepare_downloads_for_queue(
203233
self,
204234
downloads: List[Download],

frontend/api.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,10 @@ def extract_key(request, key: str, check_existence: bool = True) -> Any:
165165
except KeyError:
166166
raise InvalidKeyValue(key, value)
167167

168-
elif key in ('root_folder_id', 'root_folder', 'offset', 'limit'):
168+
elif key in (
169+
'root_folder_id', 'root_folder',
170+
'offset', 'limit', 'index'
171+
):
169172
try:
170173
value = int(value)
171174
except (ValueError, TypeError):
@@ -868,7 +871,10 @@ def api_downloads():
868871
return return_api({})
869872

870873

871-
@api.route('/activity/queue/<int:download_id>', methods=['GET', 'DELETE'])
874+
@api.route(
875+
'/activity/queue/<int:download_id>',
876+
methods=['GET', 'PUT', 'DELETE']
877+
)
872878
@error_handler
873879
@auth
874880
def api_delete_download(download_id: int):
@@ -878,6 +884,11 @@ def api_delete_download(download_id: int):
878884
result = download_handler.get_one(download_id).todict()
879885
return return_api(result)
880886

887+
elif request.method == 'PUT':
888+
index: int = extract_key(request, 'index')
889+
download_handler.set_queue_location(download_id, index)
890+
return return_api({})
891+
881892
elif request.method == 'DELETE':
882893
data: Dict[str, Any] = request.get_json(silent=True) or {}
883894
blocklist = data.get('blocklist', False)

frontend/static/css/queue.css

+22-4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ th, td {
2828
border-top: 1px solid var(--border-color);
2929
}
3030

31+
.queue-entry:nth-child(1 of .queue-entry[data-status="queued"]) .move-up-dl,
32+
.queue-entry:nth-last-child(1 of .queue-entry[data-status="queued"]) .move-down-dl,
33+
.queue-entry[data-status="downloading"] :where(.move-up-dl, .move-down-dl) {
34+
display: none;
35+
}
36+
37+
.queue-entry:nth-child(1 of .queue-entry[data-status="queued"]) .move-down-dl {
38+
margin-left: calc(20px + .75rem);
39+
}
40+
41+
.queue-entry:nth-last-child(1 of .queue-entry[data-status="queued"]) .move-up-dl {
42+
margin-right: calc(20px + 1.25rem);
43+
}
44+
45+
.queue-entry[data-status="downloading"] .remove-dl {
46+
margin-left: calc(20px + 2.75rem);
47+
}
48+
3149
.status-column {
3250
width: clamp(7.5rem, 11vw, 10rem);
3351
}
@@ -37,11 +55,11 @@ th, td {
3755
}
3856

3957
.option-column {
40-
width: 4.5rem;
58+
width: 9rem;
4159
}
4260

43-
.option-column button:nth-of-type(2) {
44-
margin-left: .5rem;
61+
.option-column button:not(:last-child) {
62+
margin-right: .5rem;
4563
}
4664

4765
.option-column button img {
@@ -52,4 +70,4 @@ th, td {
5270
main {
5371
width: calc(100vw - var(--nav-width));
5472
}
55-
}
73+
}

frontend/static/img/arrow_down.svg

+4
Loading

frontend/static/img/arrow_up.svg

+4
Loading

frontend/static/js/queue.js

+20
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ function addQueueEntry(api_key, obj) {
2626
if (obj.web_sub_title !== null)
2727
source.title += `\n\nSub Section:\n${obj.web_sub_title}`;
2828

29+
const index = [...QEls.queue.children].indexOf(entry);
30+
entry.querySelector('.move-up-dl').onclick = e => moveEntry(
31+
obj.id, index - 1, api_key
32+
);
33+
entry.querySelector('.move-down-dl').onclick = e => moveEntry(
34+
obj.id, index + 1, api_key
35+
);
2936
entry.querySelector('.remove-dl').onclick = e => deleteEntry(
3037
obj.id, api_key
3138
);
@@ -40,6 +47,7 @@ function addQueueEntry(api_key, obj) {
4047

4148
function updateQueueEntry(obj) {
4249
const tr = document.querySelector(`#queue > tr[data-id="${obj.id}"]`);
50+
tr.dataset.status = obj.status;
4351
tr.querySelector('td:nth-child(1)').innerText =
4452
obj.status.charAt(0).toUpperCase() + obj.status.slice(1);
4553
tr.querySelector('td:nth-child(4)').innerText =
@@ -71,6 +79,18 @@ function deleteAll(api_key) {
7179
sendAPI('DELETE', '/activity/queue', api_key);
7280
};
7381

82+
function moveEntry(id, index, api_key) {
83+
sendAPI('PUT', `/activity/queue/${id}`, api_key, {
84+
index: index
85+
}, {})
86+
.then(response => {
87+
if (!response.ok)
88+
return;
89+
90+
fillQueue(api_key);
91+
});
92+
}
93+
7494
function deleteEntry(id, api_key, blocklist=false) {
7595
sendAPI('DELETE', `/activity/queue/${id}`, api_key, {}, {
7696
blocklist: blocklist

frontend/templates/queue.html

+7-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
<td class="number-column"></td>
2222
<td class="number-column"></td>
2323
<td class="option-column">
24+
<button title="Move download up" class="move-up-dl">
25+
<img src="{{ url_for('static', filename='img/arrow_up.svg') }}">
26+
</button>
27+
<button title="Move download down" class="move-down-dl">
28+
<img src="{{ url_for('static', filename='img/arrow_down.svg') }}">
29+
</button>
2430
<button title="Remove download" class="remove-dl">
2531
<img src="{{ url_for('static', filename='img/delete.svg') }}">
2632
</button>
@@ -53,7 +59,7 @@
5359
<th class="number-column">Size</th>
5460
<th class="number-column">Speed</th>
5561
<th class="number-column">Progress</th>
56-
<th class="option-column">Delete</th>
62+
<th class="option-column">Actions</th>
5763
</tr>
5864
</thead>
5965
<tbody id="queue"></tbody>

0 commit comments

Comments
 (0)