mirror of
https://github.com/ae-utbm/sith.git
synced 2024-11-22 14:13:21 +00:00
Merge pull request #745 from ae-utbm/picture-zip
Add image download progress bar and fix output name of pictures
This commit is contained in:
commit
a321bd79ed
@ -28,3 +28,5 @@ $twitblue: hsl(206, 82%, 63%);
|
||||
$shadow-color: rgb(223, 223, 223);
|
||||
|
||||
$background-button-color: hsl(0, 0%, 95%);
|
||||
|
||||
$deepblue: #354a5f;
|
@ -1,7 +1,8 @@
|
||||
@import "colors";
|
||||
|
||||
$hovered-text-color: #c2c2c2;
|
||||
$text-color: white;
|
||||
|
||||
$background-color: #354a5f;
|
||||
$background-color-hovered: #283747;
|
||||
|
||||
$red-text-color: #eb2f06;
|
||||
@ -9,7 +10,7 @@ $hovered-red-text-color: #ff4d4d;
|
||||
|
||||
.header {
|
||||
box-sizing: border-box;
|
||||
background-color: $background-color;
|
||||
background-color: $deepblue;
|
||||
box-shadow: 3px 3px 3px 0 #dfdfdf;
|
||||
border-radius: 0;
|
||||
width: 100%;
|
||||
@ -98,7 +99,7 @@ $hovered-red-text-color: #ff4d4d;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
background-color: $background-color;
|
||||
background-color: $deepblue;
|
||||
width: 45px;
|
||||
height: 25px;
|
||||
padding: 0;
|
||||
@ -213,7 +214,7 @@ $hovered-red-text-color: #ff4d4d;
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-color: $background-color;
|
||||
background-color: $deepblue;
|
||||
}
|
||||
|
||||
>.options {
|
||||
|
1
core/static/core/js/jszip/jszip-utils.min.js
vendored
1
core/static/core/js/jszip/jszip-utils.min.js
vendored
@ -1 +0,0 @@
|
||||
!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.JSZipUtils=e():"undefined"!=typeof global?global.JSZipUtils=e():"undefined"!=typeof self&&(self.JSZipUtils=e())}(function(){return function o(i,f,u){function s(n,e){if(!f[n]){if(!i[n]){var t="function"==typeof require&&require;if(!e&&t)return t(n,!0);if(a)return a(n,!0);throw new Error("Cannot find module '"+n+"'")}var r=f[n]={exports:{}};i[n][0].call(r.exports,function(e){var t=i[n][1][e];return s(t||e)},r,r.exports,o,i,f,u)}return f[n].exports}for(var a="function"==typeof require&&require,e=0;e<u.length;e++)s(u[e]);return s}({1:[function(e,t,n){"use strict";var u={};function r(){try{return new window.XMLHttpRequest}catch(e){}}u._getBinaryFromXHR=function(e){return e.response||e.responseText};var s="undefined"!=typeof window&&window.ActiveXObject?function(){return r()||function(){try{return new window.ActiveXObject("Microsoft.XMLHTTP")}catch(e){}}()}:r;u.getBinaryContent=function(t,n){var e,r,o,i;"function"==typeof(n=n||{})?(i=n,n={}):"function"==typeof n.callback&&(i=n.callback),i||"undefined"==typeof Promise?(r=function(e){i(null,e)},o=function(e){i(e,null)}):e=new Promise(function(e,t){r=e,o=t});try{var f=s();f.open("GET",t,!0),"responseType"in f&&(f.responseType="arraybuffer"),f.overrideMimeType&&f.overrideMimeType("text/plain; charset=x-user-defined"),f.onreadystatechange=function(e){if(4===f.readyState)if(200===f.status||0===f.status)try{r(u._getBinaryFromXHR(f))}catch(e){o(new Error(e))}else o(new Error("Ajax error for "+t+" : "+this.status+" "+this.statusText))},n.progress&&(f.onprogress=function(e){n.progress({path:t,originalEvent:e,percent:e.loaded/e.total*100,loaded:e.loaded,total:e.total})}),f.send()}catch(e){o(new Error(e),null)}return e},t.exports=u},{}]},{},[1])(1)});
|
13
core/static/core/js/jszip/jszip.min.js
vendored
13
core/static/core/js/jszip/jszip.min.js
vendored
File diff suppressed because one or more lines are too long
28
core/static/core/js/zipjs/LICENSE
Normal file
28
core/static/core/js/zipjs/LICENSE
Normal file
@ -0,0 +1,28 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2023, Gildas Lormeau
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
core/static/core/js/zipjs/README.md
Normal file
28
core/static/core/js/zipjs/README.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Built scripts of zip.js
|
||||
|
||||
**Warning**: These files are not compatible with ES modules, i.e. they cannot be imported with `import`. Instead, import `index.js` in the root folder of the project or one of the files prefixed with `zip-` in the [`/lib`](../lib) folder (e.g. `/lib/zip-no-worker-inflate.js`).
|
||||
|
||||
- for production (minified):
|
||||
|
||||
| | [`ZipReader`](https://gildas-lormeau.github.io/zip.js/api/classes/ZipReader.html) API | [`ZipWriter`](https://gildas-lormeau.github.io/zip.js/api/classes/ZipWriter.html) API | [`zip.fs`](https://gildas-lormeau.github.io/zip.js/api/classes/FS.html) API | Web Workers | No Web Workers | Usage |
|
||||
|--------------------------------|-----------------|-----------------|--------------|-------------|----------------|-------------------------------------------------------|
|
||||
| `zip.min.js` | x | x | | x | | compression/decompression with web workers |
|
||||
| `zip-no-worker.min.js` | x | x | | | x | compression/decompression without web workers |
|
||||
| `zip-no-worker-inflate.min.js` | x | | | | x | decompression without web workers |
|
||||
| `zip-no-worker-deflate.min.js` | | x | | | x | compression without web workers |
|
||||
| `zip-full.min.js` | x | x | | x | x | compression/decompression with or without web workers |
|
||||
| `zip-fs.min.js` | x | x | x | x | | compression/decompression with web workers |
|
||||
| `zip-fs-full.min.js` | x | x | x | x | x | compression/decompression with or without web workers |
|
||||
|
||||
- for development/debugging:
|
||||
|
||||
| | `zip` API | [`zip.fs`](https://gildas-lormeau.github.io/zip.js/api/classes/FS.html) API | Web Workers | No Web Workers |
|
||||
|-----------------------|-----------|--------------|-------------|----------------|
|
||||
| `zip.js` | x | | x | |
|
||||
| `zip-full.js` | x | | x | x |
|
||||
| `zip-fs.js` | x | x | x | |
|
||||
| `zip-fs-full.js` | x | x | x | x |
|
||||
|
||||
- `z-worker.js` can be used as a web worker script if the [Content Security Policy](https://developer.mozilla.org/docs/Web/HTTP/CSP) blocks scripts loaded with a `blob:` scheme
|
||||
- `z-worker-fflate.js` is the web worker script for using [fflate](https://gildas-lormeau.github.io/zip.js/core-api.html#alternative-codec-fflate)
|
||||
- `z-worker-pako.js` is the web worker script for using [pako](https://gildas-lormeau.github.io/zip.js/core-api.html#alternative-codec-pako)
|
1
core/static/core/js/zipjs/zip-fs-full.min.js
vendored
Normal file
1
core/static/core/js/zipjs/zip-fs-full.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -105,7 +105,7 @@ a:not(.button) {
|
||||
|
||||
.collapse-header {
|
||||
color: white;
|
||||
background-color: #354a5f;
|
||||
background-color: $deepblue;
|
||||
padding: 5px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -206,34 +206,36 @@ a:not(.button) {
|
||||
width: 90%;
|
||||
margin: 20px auto 0;
|
||||
/*---------------------------------NAV---------------------------------*/
|
||||
|
||||
.btn {
|
||||
font-size: 15px;
|
||||
font-weight: normal;
|
||||
color: white;
|
||||
min-width: 60px;
|
||||
padding: 5px 10px;
|
||||
padding: 9px 13px;
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
|
||||
&.btn-blue {
|
||||
background-color: #354a5f;
|
||||
}
|
||||
|
||||
&.btn-blue:disabled {
|
||||
background-color: rgba(70, 90, 126, 0.4);
|
||||
}
|
||||
|
||||
&.btn-blue.clickable:not(:disabled):hover {
|
||||
background-color: #2c3646;
|
||||
background-color: $deepblue;
|
||||
&:not(:disabled):hover {
|
||||
background-color: darken($deepblue, 10%);
|
||||
}
|
||||
&:disabled {
|
||||
background-color: rgba(70, 90, 126, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-grey {
|
||||
background-color: grey;
|
||||
&:not(:disabled):hover {
|
||||
background-color: darken(gray, 15%);
|
||||
}
|
||||
&:disabled {
|
||||
background-color: lighten(gray, 15%);
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-grey.clickable:not(:disabled):hover {
|
||||
background-color: hsl(210, 5%, 30%);
|
||||
i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -977,7 +979,7 @@ thead td {
|
||||
}
|
||||
|
||||
thead {
|
||||
background-color: #354a5f;
|
||||
background-color: $deepblue;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,12 @@
|
||||
{%- endblock -%}
|
||||
|
||||
{% block additional_js %}
|
||||
<script defer src="{{ static('core/js/jszip/jszip.min.js') }}"></script>
|
||||
<script defer src="{{ static('core/js/jszip/jszip-utils.min.js') }}"></script>
|
||||
<script defer type="module">
|
||||
import { showSaveFilePicker } from "{{ static('core/js/native-file-system-adapter/mod.js') }}";
|
||||
window.showSaveFilePicker = showSaveFilePicker; /* Export function to normal javascript */
|
||||
</script>
|
||||
<script defer type="text/javascript" src="{{ static('core/js/zipjs/zip-fs-full.min.js') }}"></script>
|
||||
<script defer src="{{ static("core/js/alpinejs.min.js") }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
@ -19,11 +19,22 @@
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
{% if can_edit(profile, user) %}
|
||||
<button disabled id="download" onclick="download('{{ url('api:pictures') }}?users_identified={{ object.id }}')">{% trans %}Download all my pictures{% endtrans %}</button>
|
||||
{% if user.id == object.id and albums|length > 0 %}
|
||||
<div x-data="picture_download" x-cloak>
|
||||
<button
|
||||
:disabled="in_progress"
|
||||
class="btn btn-blue"
|
||||
@click="download('{{ url("api:pictures") }}?users_identified={{ object.id }}')"
|
||||
>
|
||||
<i class="fa fa-download"></i>
|
||||
{% trans %}Download all my pictures{% endtrans %}
|
||||
</button>
|
||||
<progress x-ref="progress" x-show="in_progress"></progress>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% for album, pictures in albums|items %}
|
||||
<h4>{{ album }}</h4>
|
||||
<br />
|
||||
<div class="photos">
|
||||
{% for picture in pictures %}
|
||||
{% if picture.can_be_viewed_by(user) %}
|
||||
@ -51,52 +62,61 @@
|
||||
</div>
|
||||
<br>
|
||||
{% endfor %}
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
/* Enable button once everything is loaded and if JSZip is supported */
|
||||
document.getElementById("download").disabled = !JSZip.support.blob;
|
||||
});
|
||||
async function download(url) {
|
||||
|
||||
let zip = new JSZip();
|
||||
let size = 0;
|
||||
let pictures = await (await fetch(url)).json();
|
||||
pictures.forEach(async (picture) => {
|
||||
size += picture.size;
|
||||
zip.file(
|
||||
"IMG_" + picture.date + picture.name.slice(picture.name.lastIndexOf(".")),
|
||||
new Promise(function (resolve, reject) {
|
||||
JSZipUtils.getBinaryContent(picture.full_size_url, (err, data) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve(data);
|
||||
})
|
||||
}),
|
||||
{ binary: true }
|
||||
);
|
||||
});
|
||||
|
||||
let fileHandle = await window.showSaveFilePicker({
|
||||
_preferPolyfill: false,
|
||||
suggestedName: "{%- trans -%} pictures {%- endtrans -%}.zip",
|
||||
types: {},
|
||||
excludeAcceptAllOption: false,
|
||||
})
|
||||
let writeStream = await fileHandle.createWritable();
|
||||
|
||||
await zip.generateInternalStream({
|
||||
type: "uint8array",
|
||||
streamFiles: true,
|
||||
compression: "DEFLATE",
|
||||
compressionOptions: { level: 9 }
|
||||
})
|
||||
.on("data", (data) => writeStream.write(data))
|
||||
.on("error", (err) => console.error(err))
|
||||
.on("end", () => writeStream.close())
|
||||
.resume();
|
||||
}
|
||||
</script>
|
||||
</main>
|
||||
{% endblock %}
|
||||
{% endblock content %}
|
||||
|
||||
{% block script %}
|
||||
|
||||
{{ super() }}
|
||||
{% if user.id == object.id %}
|
||||
<script>
|
||||
/**
|
||||
* @typedef Picture
|
||||
* @property {number} id
|
||||
* @property {string} name
|
||||
* @property {number} size
|
||||
* @property {string} date
|
||||
* @property {Object} author
|
||||
* @property {string} full_size_url
|
||||
* @property {string} compressed_url
|
||||
* @property {string} thumb_url
|
||||
*/
|
||||
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("picture_download", () => ({
|
||||
in_progress: false,
|
||||
|
||||
async download(url) {
|
||||
this.in_progress = true;
|
||||
const bar = this.$refs.progress;
|
||||
bar.value = 0;
|
||||
|
||||
/** @type Picture[] */
|
||||
const pictures = await (await fetch(url)).json();
|
||||
bar.max = pictures.length;
|
||||
|
||||
const fileHandle = await window.showSaveFilePicker({
|
||||
_preferPolyfill: false,
|
||||
suggestedName: "{%- trans -%} pictures {%- endtrans -%}.zip",
|
||||
types: {},
|
||||
excludeAcceptAllOption: false,
|
||||
})
|
||||
const zipWriter = new zip.ZipWriter(await fileHandle.createWritable());
|
||||
|
||||
await Promise.all(pictures.map(p => {
|
||||
const img_name = "IMG_" + p.date.replaceAll(/[:\-]/g, "_") + p.name.slice(p.name.lastIndexOf("."));
|
||||
return zipWriter.add(
|
||||
img_name,
|
||||
new zip.HttpReader(p.full_size_url),
|
||||
{level: 9, lastModDate: new Date(p.date), onstart: () => bar.value += 1}
|
||||
);
|
||||
}));
|
||||
|
||||
await zipWriter.close();
|
||||
this.in_progress = false;
|
||||
}
|
||||
}))
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock script %}
|
||||
|
@ -136,7 +136,7 @@
|
||||
right: 5px;
|
||||
padding: 5px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0px 0px 12px 2px rgb(0 0 0 / 14%);
|
||||
box-shadow: 0 0 12px 2px rgb(0 0 0 / 14%);
|
||||
background-color: white;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
@ -186,29 +186,7 @@
|
||||
}
|
||||
|
||||
#eboutic .catalog-buttons button {
|
||||
font-size: 15px!important;
|
||||
font-weight: normal;
|
||||
color: white;
|
||||
min-width: 60px;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
#eboutic .catalog-buttons .validate {
|
||||
background-color: #354a5f;
|
||||
}
|
||||
#eboutic .catalog-buttons .clear {
|
||||
background-color: gray;
|
||||
}
|
||||
#eboutic .catalog-buttons button i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
#eboutic .catalog-buttons button.validate:hover {
|
||||
background-color: #2c3646;
|
||||
}
|
||||
|
||||
#eboutic .catalog-buttons button.clear:hover {
|
||||
background-color:hsl(210,5%,30%);
|
||||
}
|
||||
|
||||
#eboutic .catalog-buttons form {
|
||||
@ -252,7 +230,7 @@
|
||||
}
|
||||
|
||||
#eboutic .product-image {
|
||||
margin-bottom: 0px;
|
||||
margin-bottom: 0;
|
||||
max-width: 70px;
|
||||
}
|
||||
}
|
||||
|
@ -62,13 +62,13 @@
|
||||
</li>
|
||||
</ul>
|
||||
<div class="catalog-buttons">
|
||||
<button @click="clear_basket()" class="clear">
|
||||
<button @click="clear_basket()" class="btn btn-grey">
|
||||
<i class="fa fa-trash"></i>
|
||||
{% trans %}Clear{% endtrans %}
|
||||
</button>
|
||||
<form method="get" action="{{ url('eboutic:command') }}">
|
||||
{% csrf_token %}
|
||||
<button class="validate">
|
||||
<button class="btn btn-blue">
|
||||
<i class="fa fa-check"></i>
|
||||
<input type="submit" value="{% trans %}Validate{% endtrans %}"/>
|
||||
</button>
|
||||
|
@ -724,8 +724,7 @@ if SENTRY_DSN:
|
||||
)
|
||||
|
||||
SITH_FRONT_DEP_VERSIONS = {
|
||||
"https://github.com/Stuk/jszip-utils": "0.1.0",
|
||||
"https://github.com/Stuk/jszip": "3.10.1",
|
||||
"https://github.com/gildas-lormeau/zip.js": "2.7.47",
|
||||
"https://github.com/jimmywarting/native-file-system-adapter": "3.0.1",
|
||||
"https://github.com/chartjs/Chart.js/": "2.6.0",
|
||||
"https://github.com/Ionaru/easy-markdown-editor/": "2.18.0",
|
||||
|
Loading…
Reference in New Issue
Block a user