wymiana lightbox, usunięcie jquery, zmiana na glightbox poprawa js i emitowanie wgrania z endpointy uploadu

This commit is contained in:
Mateusz Gruszczyński
2025-07-10 16:40:15 +02:00
parent f36739aa40
commit d8d1010aa4
15 changed files with 126 additions and 109 deletions

12
app.py
View File

@@ -322,6 +322,10 @@ def internal_error(e):
message="Wystąpił nieoczekiwany błąd. Spróbuj ponownie później."
), 500
@app.route('/favicon.ico')
def favicon_ico():
return redirect(url_for('static', filename='favicon.svg'))
@app.route('/favicon.svg')
def favicon():
svg = '''
@@ -631,6 +635,9 @@ def upload_receipt(list_id):
if request.is_json or request.headers.get('X-Requested-With') == 'XMLHttpRequest':
url = url_for('uploaded_file', filename=full_filename)
socketio.emit('receipt_added', {'url': url}, to=str(list_id))
return jsonify({'success': True, 'url': url})
flash('Wgrano paragon', 'success')
@@ -641,6 +648,7 @@ def upload_receipt(list_id):
flash('Niedozwolony format pliku', 'danger')
return redirect(request.referrer)
@app.route('/uploads/<filename>')
def uploaded_file(filename):
response = send_from_directory(app.config['UPLOAD_FOLDER'], filename)
@@ -1303,14 +1311,14 @@ def handle_add_expense(data):
'total': total
}, to=str(list_id))
@socketio.on('receipt_uploaded')
""" @socketio.on('receipt_uploaded')
def handle_receipt_uploaded(data):
list_id = data['list_id']
url = data['url']
emit('receipt_added', {
'url': url
}, to=str(list_id), include_self=False)
}, to=str(list_id), include_self=False) """
@app.cli.command('create_db')
def create_db():

View File

@@ -1,85 +1,105 @@
let receiptToastShown = false;
window.receiptToastShown = window.receiptToastShown || false;
document.addEventListener("DOMContentLoaded", function () {
const form = document.getElementById("receiptForm");
const input = document.getElementById("receiptInput");
const gallery = document.getElementById("receiptGallery");
const progressContainer = document.getElementById("progressContainer");
const progressBar = document.getElementById("progressBar");
if (!window.receiptUploaderInitialized) {
document.addEventListener("DOMContentLoaded", function () {
const form = document.getElementById("receiptForm");
const input = document.getElementById("receiptInput");
const gallery = document.getElementById("receiptGallery");
const progressContainer = document.getElementById("progressContainer");
const progressBar = document.getElementById("progressBar");
const fileLabel = document.getElementById("fileLabel");
if (!form || !input || !gallery) return;
if (!form || !input || !gallery) return;
form.addEventListener("submit", function (e) {
e.preventDefault();
const file = input.files[0];
if (!file) {
showToast("Nie wybrano pliku!", "warning");
return;
// Zmiana labela po wyborze pliku
if (input && fileLabel) {
input.addEventListener("change", function () {
if (input.files.length > 0) {
fileLabel.textContent = input.files[0].name;
} else {
fileLabel.textContent = "Wybierz zdjęcie paragonu";
}
});
}
const formData = new FormData();
formData.append("receipt", file);
form.addEventListener("submit", function (e) {
e.preventDefault();
const xhr = new XMLHttpRequest();
xhr.open("POST", form.action, true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.upload.onprogress = function (e) {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
progressBar.style.width = percent + "%";
progressBar.textContent = percent + "%";
const file = input.files[0];
if (!file) {
showToast("Nie wybrano pliku!", "warning");
return;
}
};
xhr.onloadstart = function () {
progressContainer.style.display = "block";
progressBar.style.width = "0%";
progressBar.textContent = "0%";
};
const formData = new FormData();
formData.append("receipt", file);
xhr.onloadend = function () {
progressContainer.style.display = "none";
progressBar.style.width = "0%";
progressBar.textContent = "";
input.value = "";
};
const xhr = new XMLHttpRequest();
xhr.open("POST", form.action, true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
const res = JSON.parse(xhr.responseText);
if (res.success && res.url) {
xhr.upload.onprogress = function (e) {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
progressBar.style.width = percent + "%";
progressBar.textContent = percent + "%";
}
};
fetch(window.location.href)
.then(response => response.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
const newGallery = doc.getElementById("receiptGallery");
if (newGallery) {
xhr.onloadstart = function () {
progressContainer.style.display = "block";
progressBar.style.width = "0%";
progressBar.textContent = "0%";
};
xhr.onloadend = function () {
progressContainer.style.display = "none";
progressBar.style.width = "0%";
progressBar.textContent = "";
input.value = "";
if (fileLabel) {
fileLabel.textContent = "Wybierz zdjęcie paragonu";
}
};
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
const res = JSON.parse(xhr.responseText);
if (res.success && res.url) {
fetch(window.location.href)
.then(response => response.text())
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
const newGallery = doc.getElementById("receiptGallery");
if (newGallery) {
gallery.innerHTML = newGallery.innerHTML;
if (!receiptToastShown) {
showToast("Wgrano paragon", "success");
receiptToastShown = true;
}
socket.emit("receipt_uploaded", {
list_id: LIST_ID,
url: res.url
});
}
});
} else {
showToast(res.message || "Błąd podczas wgrywania.", "danger");
}
} else {
showToast("Błąd serwera. Spróbuj ponownie.", "danger");
}
}
};
lightbox.destroy();
lightbox = GLightbox({
selector: '.glightbox'
});
xhr.send(formData);
if (!window.receiptToastShown) {
showToast("Wgrano paragon", "success");
window.receiptToastShown = true;
}
}
});
} else {
showToast(res.message || "Błąd podczas wgrywania.", "danger");
}
} else {
showToast("Błąd serwera. Spróbuj ponownie.", "danger");
}
}
};
xhr.send(formData);
});
});
});
window.receiptUploaderInitialized = true;
}

View File

@@ -78,6 +78,7 @@ socket.on('user_list', function(data) {
});
socket.on('receipt_added', function (data) {
const gallery = document.getElementById("receiptGallery");
if (!gallery) return;
@@ -93,15 +94,15 @@ socket.on('receipt_added', function (data) {
const col = document.createElement("div");
col.className = "col-6 col-md-4 col-lg-3 text-center";
col.innerHTML = `
<a href="${data.url}" data-lightbox="receipt" data-title="Paragon">
<a href="${data.url}" class="glightbox" data-gallery="receipt-gallery">
<img src="${data.url}" class="img-fluid rounded shadow-sm border border-secondary" style="max-height: 200px; object-fit: cover;">
</a>
`;
gallery.appendChild(col);
lightbox.reload();
}
});
socket.on('full_list', function (data) {
const itemsContainer = document.getElementById('items');

File diff suppressed because one or more lines are too long

1
static/lib/css/glightbox.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.lb-loader,.lightbox{text-align:center;line-height:0;position:absolute;left:0}body.lb-disable-scrolling{overflow:hidden}.lightboxOverlay{position:absolute;top:0;left:0;z-index:9999;background-color:#000;filter:alpha(Opacity=80);opacity:.8;display:none}.lightbox{width:100%;z-index:10000;font-weight:400;outline:0}.lightbox .lb-image{display:block;height:auto;max-width:inherit;max-height:none;border-radius:3px;border:4px solid #fff}.lightbox a img{border:none}.lb-outerContainer{position:relative;width:250px;height:250px;margin:0 auto;border-radius:4px;background-color:#fff}.lb-outerContainer:after{content:"";display:table;clear:both}.lb-loader{top:43%;height:25%;width:100%}.lb-cancel{display:block;width:32px;height:32px;margin:0 auto;background:url(../images/loading.gif) no-repeat}.lb-nav{position:absolute;top:0;left:0;height:100%;width:100%;z-index:10}.lb-container>.nav{left:0}.lb-nav a{outline:0;background-image:url(data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==)}.lb-next,.lb-prev{height:100%;cursor:pointer;display:block}.lb-nav a.lb-prev{width:34%;left:0;float:left;background:url(../images/prev.png) left 48% no-repeat;filter:alpha(Opacity=0);opacity:0;-webkit-transition:opacity .6s;-moz-transition:opacity .6s;-o-transition:opacity .6s;transition:opacity .6s}.lb-nav a.lb-prev:hover{filter:alpha(Opacity=100);opacity:1}.lb-nav a.lb-next{width:64%;right:0;float:right;background:url(../images/next.png) right 48% no-repeat;filter:alpha(Opacity=0);opacity:0;-webkit-transition:opacity .6s;-moz-transition:opacity .6s;-o-transition:opacity .6s;transition:opacity .6s}.lb-nav a.lb-next:hover{filter:alpha(Opacity=100);opacity:1}.lb-dataContainer{margin:0 auto;padding-top:5px;width:100%;border-bottom-left-radius:4px;border-bottom-right-radius:4px}.lb-dataContainer:after{content:"";display:table;clear:both}.lb-data{padding:0 4px;color:#ccc}.lb-data .lb-details{width:85%;float:left;text-align:left;line-height:1.1em}.lb-data .lb-caption{font-size:13px;font-weight:700;line-height:1em}.lb-data .lb-caption a{color:#4ae}.lb-data .lb-number{display:block;clear:left;padding-bottom:1em;font-size:12px;color:#999}.lb-data .lb-close{display:block;float:right;width:30px;height:30px;background:url(../images/close.png) top right no-repeat;text-align:right;outline:0;filter:alpha(Opacity=70);opacity:.7;-webkit-transition:opacity .2s;-moz-transition:opacity .2s;-o-transition:opacity .2s;transition:opacity .2s}.lb-data .lb-close:hover{cursor:pointer;filter:alpha(Opacity=100);opacity:1}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
static/lib/js/glightbox.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -8,7 +8,7 @@
<link href="{{ url_for('static_bp.serve_css', filename='style.css') }}" rel="stylesheet">
<link href="{{ url_for('static_bp.serve_css_lib', filename='bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static_bp.serve_css_lib', filename='lightbox.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static_bp.serve_css_lib', filename='glightbox.min.css') }}" rel="stylesheet">
</head>
<body class="bg-dark text-white">
@@ -67,8 +67,7 @@
{% endwith %}
});
</script>
<script src="{{ url_for('static_bp.serve_js_lib', filename='jquery-3.6.0.min.js') }}"></script>
<script src="{{ url_for('static_bp.serve_js_lib', filename='lightbox.min.js') }}"></script>
<script src="{{ url_for('static_bp.serve_js_lib', filename='glightbox.min.js') }}"></script>
<script src="{{ url_for('static_bp.serve_js_lib', filename='bootstrap.bundle.min.js') }}"></script>
<script src="{{ url_for('static_bp.serve_js_lib', filename='socket.io.min.js') }}"></script>
{% if request.endpoint != 'system_auth' %}
@@ -77,6 +76,12 @@
<script src="{{ url_for('static_bp.serve_js', filename='sockets.js') }}"></script>
{% endif %}
<script src="{{ url_for('static_bp.serve_js', filename='toasts.js') }}"></script>
<script>
let lightbox = GLightbox({
selector: '.glightbox'
});
</script>
{% block scripts %}{% endblock %}
</body>

View File

@@ -137,7 +137,7 @@ Lista: <strong>{{ list.title }}</strong>
{% if receipt_files %}
{% for file in receipt_files %}
<div class="col-6 col-md-4 col-lg-3 text-center">
<a href="{{ url_for('uploaded_file', filename=file) }}" data-lightbox="receipt" data-title="Paragon">
<a href="{{ url_for('uploaded_file', filename=file) }}" class="glightbox" data-gallery="receipt-gallery">
<img src="{{ url_for('uploaded_file', filename=file) }}" class="img-fluid rounded shadow-sm border border-secondary" style="max-height: 200px; object-fit: cover;">
</a>
</div>
@@ -149,6 +149,7 @@ Lista: <strong>{{ list.title }}</strong>
{% endif %}
</div>
<div class="modal fade" id="massAddModal" tabindex="-1" aria-labelledby="massAddModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content bg-dark text-white">

View File

@@ -76,7 +76,7 @@
{% if receipt_files %}
{% for file in receipt_files %}
<div class="col-6 col-md-4 col-lg-3 text-center">
<a href="{{ url_for('uploaded_file', filename=file) }}" data-lightbox="receipt" data-title="Paragon">
<a href="{{ url_for('uploaded_file', filename=file) }}" class="glightbox" data-gallery="receipt-gallery">
<img src="{{ url_for('uploaded_file', filename=file) }}" class="img-fluid rounded shadow-sm border border-secondary" style="max-height: 200px; object-fit: cover;">
</a>
</div>
@@ -91,12 +91,13 @@
{% if not list.is_archived %}
<hr>
<h5>📤 Dodaj zdjęcie paragonu</h5>
<form id="receiptForm" action="{{ url_for('upload_receipt', list_id=list.id) }}" method="post" enctype="multipart/form-data">
<div class="input-group mb-2">
<input type="file" name="receipt" accept="image/*" capture="environment" class="form-control bg-dark text-white border-secondary" id="receiptInput">
<button type="submit" class="btn btn-success rounded-end"> Wgraj</button>
</div>
<div id="progressContainer" class="progress mb-2" style="height: 20px; display: none;">
<form id="receiptForm" action="{{ url_for('upload_receipt', list_id=list.id) }}" method="post" enctype="multipart/form-data" class="text-center">
<label for="receiptInput" class="btn btn-outline-light w-100 py-3 mb-2 d-flex align-items-center justify-content-center gap-2">
<i class="bi bi-upload"></i> 📸 <span id="fileLabel">Wybierz zdjęcie paragonu</span>
</label>
<input type="file" name="receipt" accept="image/*" capture="environment" class="d-none" id="receiptInput">
<button type="submit" class="btn btn-success w-100 mb-2"> Wgraj paragon</button>
<div id="progressContainer" class="progress" style="height: 20px; display: none;">
<div id="progressBar" class="progress-bar bg-success fw-bold" role="progressbar" style="width: 0%;">0%</div>
</div>
</form>
@@ -133,6 +134,7 @@
<script>
setupList({{ list.id }}, '{{ current_user.username if current_user.is_authenticated else 'Gość' }}');
</script>
{% endblock %}
{% endblock %}