Skip to content

Commit ad67aa9

Browse files
authored
Merge pull request #265 from Xpirix/plugin_upload_improvements
Implement plugin upload improvements
2 parents 0c7a01c + 15c9934 commit ad67aa9

File tree

8 files changed

+320
-36
lines changed

8 files changed

+320
-36
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{% load i18n %}
2+
<div class="file-upload-zone" role="button" tabindex="0"
3+
aria-label="{% trans 'Upload file' %}">
4+
<input
5+
class="file-input"
6+
type="file"
7+
id="{{ field.html_name }}"
8+
name="{{ field.html_name }}"
9+
aria-label="{{ field.label }}"
10+
accept=".zip,.zip_,application/zip,application/x-zip-compressed"
11+
/>
12+
<span class="file-upload-icon">
13+
<i class="fas fa-cloud-upload-alt"></i>
14+
</span>
15+
<p class="file-upload-label">
16+
{% trans "Drag & drop your plugin ZIP here, or" %}&nbsp;<span class="browse-link">{% trans "browse" %}</span>
17+
</p>
18+
<p class="file-upload-hint">{% trans "Accepted format: .zip" %}</p>
19+
<div class="file-upload-selected">
20+
<span class="file-upload-selected-icon">
21+
<i class="fas fa-file-archive"></i>
22+
</span>
23+
<span class="file-upload-selected-name"></span>
24+
</div>
25+
</div>

qgis-app/plugins/templates/plugins/form_snippet.html

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,12 @@
2323
{% endif %}
2424
</div>
2525
{% elif field.field.widget|klass == 'ClearableFileInput' %}
26-
<div class="file has-name">
27-
<label class="file-label">
28-
<input
29-
class="file-input"
30-
type="file"
31-
id="{{ field.html_name }}"
32-
name="{{ field.html_name }}"
33-
/>
34-
<span class="file-cta">
35-
<span class="file-icon">
36-
<i class="fas fa-cloud-upload-alt"></i>
37-
</span>
38-
<span class="file-label"> {{ field.label }} </span>
39-
</span>
40-
<span class="file-name" id="filename"> Choose a file… </span>
41-
</label>
42-
</div>
26+
{% include "plugins/file_input_widget.html" with field=field %}
4327
{% if field.errors %}
4428
{% for error in field.errors %}
4529
<p class="help is-danger has-text-weight-medium">{{ error }}</p>
4630
{% endfor %}
47-
{% endif %}
31+
{% endif %}
4832
{% elif field.field.widget|klass == 'Textarea' %}
4933
<div class="field">
5034
<label class="label">{{ field.label_tag }}</label>
@@ -132,19 +116,42 @@
132116
<div class="help has-text-grey">{{ field.help_text | safe }}</div>
133117
</div>
134118
{% endfor %}
119+
{% if show_upload_checklist %}
120+
<div class="mt-5">
121+
<h4 class="title is-5">{% trans "Pre-upload Checklist" %}</h4>
122+
<p class="mb-3">{% trans "Please confirm each item below before uploading. The button will become active once all items are checked." %}</p>
123+
<div class="field">
124+
<label class="checkbox">
125+
<input type="checkbox" class="upload-checklist-item">
126+
&nbsp;{% trans "My plugin ZIP file follows the correct QGIS plugin structure (e.g. a top-level folder containing <code>metadata.txt</code> and <code>__init__.py</code>)." %}
127+
</label>
128+
</div>
129+
<div class="field">
130+
<label class="checkbox">
131+
<input type="checkbox" class="upload-checklist-item">
132+
&nbsp;{% trans "The plugin repository is not empty and contains the same source code as the ZIP file, excluding compiled files (e.g. <code>__pycache__</code>)." %}
133+
</label>
134+
</div>
135+
<div class="field">
136+
<label class="checkbox">
137+
<input type="checkbox" class="upload-checklist-item">
138+
&nbsp;{% trans "My plugin includes a meaningful description written in English." %}
139+
</label>
140+
</div>
141+
<div class="field">
142+
<label class="checkbox">
143+
<input type="checkbox" class="upload-checklist-item">
144+
&nbsp;{% trans "All metadata links (homepage, tracker, repository) are valid and publicly accessible." %}
145+
</label>
146+
</div>
147+
<div class="field">
148+
<label class="checkbox">
149+
<input type="checkbox" class="upload-checklist-item">
150+
&nbsp;{% trans "I have tested this plugin version with QGIS and it works as expected." %}
151+
</label>
152+
</div>
153+
</div>
154+
{% endif %}
135155
</fieldset>
136156

137-
<script>
138-
document.addEventListener("DOMContentLoaded", function () {
139-
const fileInput = document.querySelector(".file-input");
140-
const fileName = document.querySelector("#filename");
141157

142-
fileInput.addEventListener("change", function () {
143-
if (fileInput.files.length > 0) {
144-
fileName.textContent = fileInput.files[0].name;
145-
} else {
146-
fileName.textContent = "Choose a file…";
147-
}
148-
});
149-
});
150-
</script>

qgis-app/plugins/templates/plugins/plugin_upload.html

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ <h2>{% trans "Upload a plugin" %}</h2>
2222
{% endif %}
2323

2424
<form action="" method="post" enctype="multipart/form-data" class="horizontal">{% csrf_token %}
25-
{% include "plugins/form_snippet.html" %}
25+
{% include "plugins/form_snippet.html" with show_upload_checklist=True %}
26+
2627
<div class="mt-3 has-text-centered">
27-
<button class="button is-success is-medium" type="submit">
28+
<button id="upload-submit-btn" class="button is-success is-medium" type="submit" disabled>
2829
<span class="icon is-small">
2930
<i class="fas fa-upload"></i>
3031
</span>
@@ -34,6 +35,24 @@ <h2>{% trans "Upload a plugin" %}</h2>
3435
</button>
3536
</div>
3637
</form>
38+
39+
<script>
40+
(function () {
41+
var checkboxes = document.querySelectorAll('.upload-checklist-item');
42+
var submitBtn = document.getElementById('upload-submit-btn');
43+
44+
function updateSubmitButton() {
45+
var allChecked = Array.prototype.every.call(checkboxes, function (cb) {
46+
return cb.checked;
47+
});
48+
submitBtn.disabled = !allChecked;
49+
}
50+
51+
checkboxes.forEach(function (cb) {
52+
cb.addEventListener('change', updateSubmitButton);
53+
});
54+
})();
55+
</script>
3756
<div class="m-5">
3857
</div>
3958
<div class="container rich">

qgis-app/plugins/templates/plugins/version_form.html

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,33 @@ <h2>{{ form_title }} {{ version }}</h2>
1212

1313
<form action="" method="post" enctype="multipart/form-data" class="horizontal">
1414
{% csrf_token %}
15-
{% include "plugins/form_snippet.html" %}
15+
{% include "plugins/form_snippet.html" with show_upload_checklist=True %}
16+
1617
<div class="form-actions has-text-right mt-3">
17-
<button class="button is-success" type="submit">
18+
<button id="upload-submit-btn" class="button is-success" type="submit" disabled>
1819
<span class="icon">
19-
<i class="fas fa-save"></i> <!-- FontAwesome save icon -->
20+
<i class="fas fa-save"></i>
2021
</span>
2122
<span>{% trans "Save" %}</span>
2223
</button>
2324
</div>
2425
</form>
26+
27+
<script>
28+
(function () {
29+
var checkboxes = document.querySelectorAll('.upload-checklist-item');
30+
var submitBtn = document.getElementById('upload-submit-btn');
31+
32+
function updateSubmitButton() {
33+
var allChecked = Array.prototype.every.call(checkboxes, function (cb) {
34+
return cb.checked;
35+
});
36+
submitBtn.disabled = !allChecked;
37+
}
38+
39+
checkboxes.forEach(function (cb) {
40+
cb.addEventListener('change', updateSubmitButton);
41+
});
42+
})();
43+
</script>
2544
{% endblock %}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* File Upload Drop Zone
3+
*
4+
* Enhances every .file-upload-zone element with drag-and-drop support and
5+
* visual feedback. Works with the file_input_widget.html partial.
6+
*/
7+
(function () {
8+
'use strict';
9+
10+
function initFileUploadZone(zone) {
11+
var input = zone.querySelector('input[type="file"]');
12+
var selectedEl = zone.querySelector('.file-upload-selected');
13+
var selectedName = zone.querySelector('.file-upload-selected-name');
14+
15+
if (!input) return;
16+
17+
function setFile(fileName) {
18+
if (fileName) {
19+
zone.classList.add('has-file');
20+
if (selectedName) selectedName.textContent = fileName;
21+
} else {
22+
zone.classList.remove('has-file');
23+
if (selectedName) selectedName.textContent = '';
24+
}
25+
}
26+
27+
// Native file picker selection
28+
input.addEventListener('change', function () {
29+
if (input.files && input.files.length > 0) {
30+
setFile(input.files[0].name);
31+
} else {
32+
setFile(null);
33+
}
34+
});
35+
36+
// Drag-and-drop support
37+
['dragenter', 'dragover'].forEach(function (eventName) {
38+
zone.addEventListener(eventName, function (e) {
39+
e.preventDefault();
40+
e.stopPropagation();
41+
zone.classList.add('is-dragover');
42+
});
43+
});
44+
45+
['dragleave', 'dragend'].forEach(function (eventName) {
46+
zone.addEventListener(eventName, function (e) {
47+
e.preventDefault();
48+
e.stopPropagation();
49+
zone.classList.remove('is-dragover');
50+
});
51+
});
52+
53+
zone.addEventListener('drop', function (e) {
54+
e.preventDefault();
55+
e.stopPropagation();
56+
zone.classList.remove('is-dragover');
57+
58+
var files = e.dataTransfer && e.dataTransfer.files;
59+
if (!files || files.length === 0) return;
60+
61+
// Only accept the first file; rely on the accept attribute for filtering
62+
var accept = input.getAttribute('accept');
63+
if (accept) {
64+
var accepted = accept.split(',').map(function (a) { return a.trim().toLowerCase(); });
65+
var file = files[0];
66+
var ext = '.' + file.name.split('.').pop().toLowerCase();
67+
var mime = file.type.toLowerCase();
68+
var allowed = accepted.some(function (pattern) {
69+
return pattern === ext || pattern === mime || (pattern.endsWith('/*') && mime.startsWith(pattern.slice(0, -1)));
70+
});
71+
if (!allowed) return;
72+
}
73+
74+
// Transfer file to the real input via DataTransfer
75+
try {
76+
var dt = new DataTransfer();
77+
dt.items.add(files[0]);
78+
input.files = dt.files;
79+
setFile(files[0].name);
80+
input.dispatchEvent(new Event('change', { bubbles: true }));
81+
} catch (err) {
82+
// DataTransfer not supported — silently ignore
83+
}
84+
});
85+
}
86+
87+
document.addEventListener('DOMContentLoaded', function () {
88+
document.querySelectorAll('.file-upload-zone').forEach(function (zone) {
89+
initFileUploadZone(zone);
90+
});
91+
});
92+
})();

qgis-app/static/js/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require('../style/scss/style.scss');
22

3+
import './file_upload_widget.js';
34
import 'datatables.net';
45
import 'datatables.net-dt';
56

0 commit comments

Comments
 (0)