mirror of https://github.com/thumbsup/thumbsup
Merge 0e2c387054
into 38fe01ea69
commit
32a04e9ace
@ -0,0 +1,19 @@
|
||||
const path = require('node:path')
|
||||
const fs = require('node:fs')
|
||||
const Observable = require('zen-observable')
|
||||
const trace = require('debug')('thumbsup:trace')
|
||||
const debug = require('debug')('thumbsup:debug')
|
||||
const error = require('debug')('thumbsup:error')
|
||||
|
||||
exports.run = function (files, opts) {
|
||||
return new Observable(observer => {
|
||||
|
||||
files.map(f => {
|
||||
filePath = path.join(opts.output, f.output.download.path)
|
||||
stats = fs.statSync(filePath)
|
||||
f.fileSize = {download: stats.size}
|
||||
})
|
||||
observer.complete()
|
||||
|
||||
})
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>No theme applied js-download</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>No theme applied js-download<h1>
|
||||
<p>This is just the js-download base which contains common files for album-download == 'js' </p>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,44 @@
|
||||
{{!-- Do not include this internal partial directly, include js-download partial instead --}}
|
||||
|
||||
<!-- JS Download -->
|
||||
<div class="js-download" id="jsDownload">
|
||||
JS Download not supported in this browser.
|
||||
Requires https and a compatible <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker#browser_compatibility">browser</a>, e.g. Chrome on Desktop
|
||||
</div>
|
||||
<script type="module">
|
||||
import {downloadImages} from './{{relative 'public/downloadImages.js'}}'
|
||||
var download_images = [
|
||||
{{#each album.files}}
|
||||
{ url: "{{relative urls.download}}", size: {{fileSize.download}} },
|
||||
{{/each}}
|
||||
]
|
||||
const divEl = document.getElementById('jsDownload')
|
||||
const statusDiv = document.createElement('div')
|
||||
statusDiv.classList?.add('status')
|
||||
|
||||
const download = async (override) => {
|
||||
const downloadResult = await downloadImages(download_images, statusDiv, override)
|
||||
// Log for debugging while experimental
|
||||
console.log(downloadResult)
|
||||
}
|
||||
|
||||
if(window.showDirectoryPicker !== undefined)
|
||||
{
|
||||
const dlDiv = document.createElement('div')
|
||||
dlDiv.classList.add('download')
|
||||
dlDiv.innerHTML = '<label for="dlOverride">Override existing files?</label>'
|
||||
|
||||
const overrideInput = document.createElement('input')
|
||||
overrideInput.type = 'checkbox';
|
||||
overrideInput.id = 'dlOverride'
|
||||
dlDiv.appendChild(overrideInput)
|
||||
|
||||
const button = document.createElement('button')
|
||||
button.innerHTML = 'Download all'
|
||||
button.addEventListener('click', () => download(overrideInput.checked))
|
||||
dlDiv.appendChild(button)
|
||||
|
||||
divEl.replaceChildren(dlDiv, statusDiv)
|
||||
}
|
||||
|
||||
</script>
|
@ -0,0 +1,74 @@
|
||||
//Taken from https://github.com/ccvca/js-multi-file-download/blob/bcafa4a947ec19286c217231b6383a0e6bea44fc/HtmlJsExample/downloadImages.js
|
||||
|
||||
import { DownloadFiles, FileState } from './multi-file-download/multi-file-download.js'
|
||||
|
||||
// Download images and show result in a div
|
||||
export async function downloadImages(imageList, statusDiv, overrideExistingFile) {
|
||||
const dirPickOpts = {
|
||||
mode: 'readwrite',
|
||||
//startIn: 'pictures'
|
||||
};
|
||||
const dirHandle = await window.showDirectoryPicker(dirPickOpts);
|
||||
|
||||
// Setup HTML Status display
|
||||
let dlFiles = {};
|
||||
const summary = document.createElement('div');
|
||||
summary.classList.add('summary');
|
||||
statusDiv.replaceChildren(summary); // Clear all (old) childs
|
||||
|
||||
const updateSummary = () => {
|
||||
const imgCnt = Object.keys(dlFiles).reduce(
|
||||
(accumulator, url) => dlFiles[url]?.state !== FileState.STARTED ? accumulator + 1 : accumulator
|
||||
, 0
|
||||
);
|
||||
summary.innerHTML = `${imgCnt} / ${imageList.length}`;
|
||||
};
|
||||
updateSummary();
|
||||
|
||||
// Callback with download progress
|
||||
const onStateUpdate = (url, state) => {
|
||||
let element;
|
||||
if (url in dlFiles) {
|
||||
element = dlFiles[url];
|
||||
element = { ...element, ...state };
|
||||
}
|
||||
else if (state.state !== undefined) {
|
||||
element = { state: state.state, ...state };
|
||||
element.htmlEl = document.createElement('div');
|
||||
element.htmlEl.classList.add('progress')
|
||||
statusDiv.appendChild(element.htmlEl);
|
||||
} else {
|
||||
console.error('Stored DlFilesState broken.');
|
||||
return;
|
||||
}
|
||||
|
||||
let text = `${url}`;
|
||||
if (element.state != FileState.STARTED) {
|
||||
text += ` ${FileState[element.state]}`
|
||||
}
|
||||
if (state.progress?.percent !== undefined) {
|
||||
text += ` ${+state.progress?.percent.toFixed(1)} %`;
|
||||
}
|
||||
|
||||
if (state.error) {
|
||||
element.htmlEl.classList.add('error');
|
||||
console.error(state.error);
|
||||
text += ` ${state.error.message}`
|
||||
}
|
||||
element.htmlEl.innerHTML = text;
|
||||
|
||||
if (element.state === FileState.COMPLETED_DOWNLOAD || element.state === FileState.SKIPPED_EXIST){
|
||||
statusDiv.removeChild(element.htmlEl);
|
||||
}
|
||||
dlFiles = { ...dlFiles, [url]: element };
|
||||
updateSummary();
|
||||
};
|
||||
|
||||
await DownloadFiles(dirHandle, imageList, {
|
||||
onStateUpdate: onStateUpdate,
|
||||
overrideExistingFile: overrideExistingFile
|
||||
});
|
||||
|
||||
// Return final state
|
||||
return dlFiles;
|
||||
};
|
@ -0,0 +1,159 @@
|
||||
var m = Object.defineProperty;
|
||||
var v = (e, t, r) => t in e ? m(e, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : e[t] = r;
|
||||
var x = (e, t, r) => (v(e, typeof t != "symbol" ? t + "" : t, r), r);
|
||||
var s;
|
||||
((e) => {
|
||||
class t extends Error {
|
||||
constructor(a) {
|
||||
super(a);
|
||||
}
|
||||
}
|
||||
e.MetadataError = t;
|
||||
class r extends Error {
|
||||
constructor(a) {
|
||||
super(a);
|
||||
}
|
||||
}
|
||||
e.DownloadError = r;
|
||||
class l extends Error {
|
||||
constructor(a) {
|
||||
super(a);
|
||||
}
|
||||
}
|
||||
e.FileExistError = l;
|
||||
class u extends Error {
|
||||
constructor(a) {
|
||||
super(a);
|
||||
}
|
||||
}
|
||||
e.InternalError = u;
|
||||
class c extends Error {
|
||||
constructor(n, d) {
|
||||
super(n);
|
||||
x(this, "innerException");
|
||||
this.innerException = d;
|
||||
}
|
||||
}
|
||||
e.GeneralError = c;
|
||||
})(s || (s = {}));
|
||||
function b(e) {
|
||||
return new URL(e, window.location.href).pathname.split("/").pop();
|
||||
}
|
||||
async function D(e, t, r) {
|
||||
try {
|
||||
return (await (await e.getFileHandle(t)).getFile()).size === r;
|
||||
} catch {
|
||||
return !1;
|
||||
}
|
||||
}
|
||||
var y = /* @__PURE__ */ ((e) => (e[e.SKIPPED_EXIST = 1] = "SKIPPED_EXIST", e[e.DOWNLOADED = 2] = "DOWNLOADED", e))(y || {});
|
||||
async function S(e, t, r = {}) {
|
||||
const l = t.fileName === void 0 ? b(t.url) : t.fileName;
|
||||
if (l === void 0)
|
||||
throw new s.MetadataError("Could not determine filename.");
|
||||
if (t.size !== void 0 && await D(e, l, t.size))
|
||||
return 1;
|
||||
if (r.overrideExistingFile !== !0)
|
||||
try {
|
||||
throw await e.getFileHandle(l, { create: !1 }), new s.FileExistError(`File: ${l} does already exist.`);
|
||||
} catch (a) {
|
||||
const n = a;
|
||||
if (a instanceof s.FileExistError)
|
||||
throw a;
|
||||
if (n.name === void 0 || n.name !== "NotFoundError")
|
||||
throw new s.FileExistError(`File: ${l} does already exist. Exeption: ${n.message}`);
|
||||
}
|
||||
const u = new AbortController(), c = await fetch(t.url, { signal: u.signal });
|
||||
if (!c.ok)
|
||||
throw new s.DownloadError(`Error while downloading: ${c.status} - ${c.statusText}`);
|
||||
if (c.body === null)
|
||||
throw new s.DownloadError("No data");
|
||||
let o = c.body;
|
||||
if (r.progress !== void 0) {
|
||||
let a = 0;
|
||||
const n = c.headers.get("content-length"), d = Number.parseInt(n ?? "") || void 0, f = new TransformStream(
|
||||
{
|
||||
transform(w, E) {
|
||||
a += w.length;
|
||||
let i = d !== void 0 ? a / d * 100 : void 0;
|
||||
if (r.progress !== void 0) {
|
||||
try {
|
||||
r.progress(a, d, i);
|
||||
} catch (g) {
|
||||
console.log(g);
|
||||
}
|
||||
E.enqueue(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
o = o.pipeThrough(f);
|
||||
}
|
||||
try {
|
||||
const n = await (await e.getFileHandle(l, { create: !0 })).createWritable();
|
||||
await o.pipeTo(n);
|
||||
} catch (a) {
|
||||
throw u.abort(), new s.GeneralError(`Download of file ${l} failed due to an exception: ${a == null ? void 0 : a.message}`, a);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
var T = /* @__PURE__ */ ((e) => (e[e.STARTED = 0] = "STARTED", e[e.COMPLETED_DOWNLOAD = 1] = "COMPLETED_DOWNLOAD", e[e.SKIPPED_EXIST = 2] = "SKIPPED_EXIST", e[e.ERROR = 3] = "ERROR", e))(T || {});
|
||||
async function I(e, t, r) {
|
||||
var c, o, a, n;
|
||||
r === void 0 && (r = {});
|
||||
const l = new AbortController(), u = r.abortSignal === void 0 ? l.signal : r.abortSignal;
|
||||
for (const d of t) {
|
||||
if (u.aborted)
|
||||
break;
|
||||
const f = r.onStateUpdate === void 0 ? void 0 : (E, i, g) => {
|
||||
var h;
|
||||
(h = r == null ? void 0 : r.onStateUpdate) == null || h.call(r, d.url, {
|
||||
progress: {
|
||||
bytes: E,
|
||||
totalBytes: i,
|
||||
percent: g
|
||||
}
|
||||
});
|
||||
}, w = {
|
||||
overrideExistingFile: r.overrideExistingFile,
|
||||
progress: f
|
||||
};
|
||||
(c = r == null ? void 0 : r.onStateUpdate) == null || c.call(r, d.url, {
|
||||
state: 0
|
||||
/* STARTED */
|
||||
});
|
||||
try {
|
||||
const E = await S(e, d, w);
|
||||
switch (E) {
|
||||
case 2:
|
||||
(o = r == null ? void 0 : r.onStateUpdate) == null || o.call(r, d.url, {
|
||||
state: 1
|
||||
/* COMPLETED_DOWNLOAD */
|
||||
});
|
||||
break;
|
||||
case 1:
|
||||
(a = r == null ? void 0 : r.onStateUpdate) == null || a.call(r, d.url, {
|
||||
state: 2
|
||||
/* SKIPPED_EXIST */
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new s.InternalError(`Unknown return value from download function: ${E} `);
|
||||
}
|
||||
} catch (E) {
|
||||
const i = E;
|
||||
(n = r == null ? void 0 : r.onStateUpdate) == null || n.call(r, d.url, {
|
||||
state: 3,
|
||||
error: i
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
export {
|
||||
S as DownloadFile,
|
||||
y as DownloadFileRet,
|
||||
I as DownloadFiles,
|
||||
s as Exceptions,
|
||||
T as FileState,
|
||||
D as VerifyFileSize
|
||||
};
|
@ -0,0 +1,27 @@
|
||||
/* Default style for js-download.
|
||||
Use {{> js-download-default-style}} in album.hbs to include it.
|
||||
*/
|
||||
#jsDownload {
|
||||
div.download {
|
||||
font-size: larger;
|
||||
|
||||
label, input, button {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
button {
|
||||
border-width: 1pt;
|
||||
background-color: lightgrey;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
div.status {
|
||||
margin-left: .5rem;
|
||||
|
||||
div.summary{
|
||||
font-size: larger;
|
||||
}
|
||||
div.error{
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{{#compare @root.gallery.albumDownload '==' 'js'}}
|
||||
<link rel="stylesheet" href="{{relative 'public/jsDownload.css'}}" />
|
||||
{{/compare}}
|
@ -0,0 +1,4 @@
|
||||
{{#compare @root.gallery.albumDownload '==' 'js'}}
|
||||
{{!-- Include the code from 'theme-base-js-download' --}}
|
||||
{{> js-download-internal}}
|
||||
{{/compare}}
|
Loading…
Reference in New Issue