mirror of
https://github.com/ae-utbm/sith.git
synced 2024-11-10 00:03:24 +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);
|
$shadow-color: rgb(223, 223, 223);
|
||||||
|
|
||||||
$background-button-color: hsl(0, 0%, 95%);
|
$background-button-color: hsl(0, 0%, 95%);
|
||||||
|
|
||||||
|
$deepblue: #354a5f;
|
@ -1,7 +1,8 @@
|
|||||||
|
@import "colors";
|
||||||
|
|
||||||
$hovered-text-color: #c2c2c2;
|
$hovered-text-color: #c2c2c2;
|
||||||
$text-color: white;
|
$text-color: white;
|
||||||
|
|
||||||
$background-color: #354a5f;
|
|
||||||
$background-color-hovered: #283747;
|
$background-color-hovered: #283747;
|
||||||
|
|
||||||
$red-text-color: #eb2f06;
|
$red-text-color: #eb2f06;
|
||||||
@ -9,7 +10,7 @@ $hovered-red-text-color: #ff4d4d;
|
|||||||
|
|
||||||
.header {
|
.header {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: $background-color;
|
background-color: $deepblue;
|
||||||
box-shadow: 3px 3px 3px 0 #dfdfdf;
|
box-shadow: 3px 3px 3px 0 #dfdfdf;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -98,7 +99,7 @@ $hovered-red-text-color: #ff4d4d;
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: $background-color;
|
background-color: $deepblue;
|
||||||
width: 45px;
|
width: 45px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -213,7 +214,7 @@ $hovered-red-text-color: #ff4d4d;
|
|||||||
background-position: center center;
|
background-position: center center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-color: $background-color;
|
background-color: $deepblue;
|
||||||
}
|
}
|
||||||
|
|
||||||
>.options {
|
>.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 {
|
.collapse-header {
|
||||||
color: white;
|
color: white;
|
||||||
background-color: #354a5f;
|
background-color: $deepblue;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -206,34 +206,36 @@ a:not(.button) {
|
|||||||
width: 90%;
|
width: 90%;
|
||||||
margin: 20px auto 0;
|
margin: 20px auto 0;
|
||||||
/*---------------------------------NAV---------------------------------*/
|
/*---------------------------------NAV---------------------------------*/
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
color: white;
|
color: white;
|
||||||
min-width: 60px;
|
padding: 9px 13px;
|
||||||
padding: 5px 10px;
|
|
||||||
border: none;
|
border: none;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&.btn-blue {
|
&.btn-blue {
|
||||||
background-color: #354a5f;
|
background-color: $deepblue;
|
||||||
|
&:not(:disabled):hover {
|
||||||
|
background-color: darken($deepblue, 10%);
|
||||||
}
|
}
|
||||||
|
&:disabled {
|
||||||
&.btn-blue:disabled {
|
|
||||||
background-color: rgba(70, 90, 126, 0.4);
|
background-color: rgba(70, 90, 126, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn-blue.clickable:not(:disabled):hover {
|
|
||||||
background-color: #2c3646;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn-grey {
|
&.btn-grey {
|
||||||
background-color: grey;
|
background-color: grey;
|
||||||
|
&:not(:disabled):hover {
|
||||||
|
background-color: darken(gray, 15%);
|
||||||
|
}
|
||||||
|
&:disabled {
|
||||||
|
background-color: lighten(gray, 15%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.btn-grey.clickable:not(:disabled):hover {
|
i {
|
||||||
background-color: hsl(210, 5%, 30%);
|
margin-right: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -977,7 +979,7 @@ thead td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thead {
|
thead {
|
||||||
background-color: #354a5f;
|
background-color: $deepblue;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,12 @@
|
|||||||
{%- endblock -%}
|
{%- endblock -%}
|
||||||
|
|
||||||
{% block additional_js %}
|
{% 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">
|
<script defer type="module">
|
||||||
import { showSaveFilePicker } from "{{ static('core/js/native-file-system-adapter/mod.js') }}";
|
import { showSaveFilePicker } from "{{ static('core/js/native-file-system-adapter/mod.js') }}";
|
||||||
window.showSaveFilePicker = showSaveFilePicker; /* Export function to normal javascript */
|
window.showSaveFilePicker = showSaveFilePicker; /* Export function to normal javascript */
|
||||||
</script>
|
</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 %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
@ -19,11 +19,22 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main>
|
<main>
|
||||||
{% if can_edit(profile, user) %}
|
{% if user.id == object.id and albums|length > 0 %}
|
||||||
<button disabled id="download" onclick="download('{{ url('api:pictures') }}?users_identified={{ object.id }}')">{% trans %}Download all my pictures{% endtrans %}</button>
|
<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 %}
|
{% endif %}
|
||||||
{% for album, pictures in albums|items %}
|
{% for album, pictures in albums|items %}
|
||||||
<h4>{{ album }}</h4>
|
<h4>{{ album }}</h4>
|
||||||
|
<br />
|
||||||
<div class="photos">
|
<div class="photos">
|
||||||
{% for picture in pictures %}
|
{% for picture in pictures %}
|
||||||
{% if picture.can_be_viewed_by(user) %}
|
{% if picture.can_be_viewed_by(user) %}
|
||||||
@ -51,52 +62,61 @@
|
|||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</main>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
|
||||||
|
{{ super() }}
|
||||||
|
{% if user.id == object.id %}
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
/**
|
||||||
/* Enable button once everything is loaded and if JSZip is supported */
|
* @typedef Picture
|
||||||
document.getElementById("download").disabled = !JSZip.support.blob;
|
* @property {number} id
|
||||||
});
|
* @property {string} name
|
||||||
async function download(url) {
|
* @property {number} size
|
||||||
|
* @property {string} date
|
||||||
|
* @property {Object} author
|
||||||
|
* @property {string} full_size_url
|
||||||
|
* @property {string} compressed_url
|
||||||
|
* @property {string} thumb_url
|
||||||
|
*/
|
||||||
|
|
||||||
let zip = new JSZip();
|
document.addEventListener("alpine:init", () => {
|
||||||
let size = 0;
|
Alpine.data("picture_download", () => ({
|
||||||
let pictures = await (await fetch(url)).json();
|
in_progress: false,
|
||||||
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({
|
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,
|
_preferPolyfill: false,
|
||||||
suggestedName: "{%- trans -%} pictures {%- endtrans -%}.zip",
|
suggestedName: "{%- trans -%} pictures {%- endtrans -%}.zip",
|
||||||
types: {},
|
types: {},
|
||||||
excludeAcceptAllOption: false,
|
excludeAcceptAllOption: false,
|
||||||
})
|
})
|
||||||
let writeStream = await fileHandle.createWritable();
|
const zipWriter = new zip.ZipWriter(await fileHandle.createWritable());
|
||||||
|
|
||||||
await zip.generateInternalStream({
|
await Promise.all(pictures.map(p => {
|
||||||
type: "uint8array",
|
const img_name = "IMG_" + p.date.replaceAll(/[:\-]/g, "_") + p.name.slice(p.name.lastIndexOf("."));
|
||||||
streamFiles: true,
|
return zipWriter.add(
|
||||||
compression: "DEFLATE",
|
img_name,
|
||||||
compressionOptions: { level: 9 }
|
new zip.HttpReader(p.full_size_url),
|
||||||
})
|
{level: 9, lastModDate: new Date(p.date), onstart: () => bar.value += 1}
|
||||||
.on("data", (data) => writeStream.write(data))
|
);
|
||||||
.on("error", (err) => console.error(err))
|
}));
|
||||||
.on("end", () => writeStream.close())
|
|
||||||
.resume();
|
await zipWriter.close();
|
||||||
|
this.in_progress = false;
|
||||||
}
|
}
|
||||||
|
}))
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</main>
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock script %}
|
||||||
|
@ -136,7 +136,7 @@
|
|||||||
right: 5px;
|
right: 5px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-radius: 50%;
|
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;
|
background-color: white;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
@ -186,29 +186,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#eboutic .catalog-buttons button {
|
#eboutic .catalog-buttons button {
|
||||||
font-size: 15px!important;
|
|
||||||
font-weight: normal;
|
|
||||||
color: white;
|
|
||||||
min-width: 60px;
|
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 {
|
#eboutic .catalog-buttons form {
|
||||||
@ -252,7 +230,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#eboutic .product-image {
|
#eboutic .product-image {
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0;
|
||||||
max-width: 70px;
|
max-width: 70px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,13 +62,13 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="catalog-buttons">
|
<div class="catalog-buttons">
|
||||||
<button @click="clear_basket()" class="clear">
|
<button @click="clear_basket()" class="btn btn-grey">
|
||||||
<i class="fa fa-trash"></i>
|
<i class="fa fa-trash"></i>
|
||||||
{% trans %}Clear{% endtrans %}
|
{% trans %}Clear{% endtrans %}
|
||||||
</button>
|
</button>
|
||||||
<form method="get" action="{{ url('eboutic:command') }}">
|
<form method="get" action="{{ url('eboutic:command') }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="validate">
|
<button class="btn btn-blue">
|
||||||
<i class="fa fa-check"></i>
|
<i class="fa fa-check"></i>
|
||||||
<input type="submit" value="{% trans %}Validate{% endtrans %}"/>
|
<input type="submit" value="{% trans %}Validate{% endtrans %}"/>
|
||||||
</button>
|
</button>
|
||||||
|
@ -724,8 +724,7 @@ if SENTRY_DSN:
|
|||||||
)
|
)
|
||||||
|
|
||||||
SITH_FRONT_DEP_VERSIONS = {
|
SITH_FRONT_DEP_VERSIONS = {
|
||||||
"https://github.com/Stuk/jszip-utils": "0.1.0",
|
"https://github.com/gildas-lormeau/zip.js": "2.7.47",
|
||||||
"https://github.com/Stuk/jszip": "3.10.1",
|
|
||||||
"https://github.com/jimmywarting/native-file-system-adapter": "3.0.1",
|
"https://github.com/jimmywarting/native-file-system-adapter": "3.0.1",
|
||||||
"https://github.com/chartjs/Chart.js/": "2.6.0",
|
"https://github.com/chartjs/Chart.js/": "2.6.0",
|
||||||
"https://github.com/Ionaru/easy-markdown-editor/": "2.18.0",
|
"https://github.com/Ionaru/easy-markdown-editor/": "2.18.0",
|
||||||
|
Loading…
Reference in New Issue
Block a user