| |
|
|
| (() => { |
|
|
| if (typeof TransformStream == "undefined") { |
| const script = document.createElement("script"); |
| script.src = "lib/web-streams-polyfill.min.js"; |
| document.body.appendChild(script); |
| } |
|
|
| const model = (() => { |
|
|
| return { |
| getEntries(file, options) { |
| return (new zip.ZipReader(new zip.BlobReader(file))).getEntries(options); |
| }, |
| async getURL(entry, options) { |
| return URL.createObjectURL(await entry.getData(new zip.BlobWriter(), options)); |
| } |
| }; |
|
|
| })(); |
|
|
| (() => { |
|
|
| const appContainer = document.getElementById("container"); |
| const fileInput = document.getElementById("file-input"); |
| const encodingInput = document.getElementById("encoding-input"); |
| const fileInputButton = document.getElementById("file-input-button"); |
| const passwordInput = document.getElementById("password-input"); |
| let fileList = document.getElementById("file-list"); |
| let entries; |
| let selectedFile; |
| passwordInput.onchange = async () => fileList.querySelectorAll("a[download]").forEach(anchor => anchor.download = ""); |
| fileInput.onchange = selectFile; |
| encodingInput.onchange = selectEncoding; |
| appContainer.onclick = downloadFile; |
| fileInputButton.onclick = () => fileInput.dispatchEvent(new MouseEvent("click")); |
|
|
| async function downloadFile(event) { |
| const target = event.target; |
| let href = target.getAttribute("href"); |
| if (target.dataset.entryIndex !== undefined && !target.download && !href) { |
| target.removeAttribute("href"); |
| event.preventDefault(); |
| try { |
| await download(entries[Number(target.dataset.entryIndex)], target.parentElement.parentElement, target); |
| href = target.getAttribute("href"); |
| } catch (error) { |
| alert(error); |
| } |
| target.setAttribute("href", href); |
| } |
| } |
|
|
| async function selectFile() { |
| try { |
| fileInputButton.disabled = true; |
| encodingInput.disabled = true; |
| selectedFile = fileInput.files[0]; |
| await loadFiles(); |
| } catch (error) { |
| alert(error); |
| } finally { |
| fileInputButton.disabled = false; |
| fileInput.value = ""; |
| } |
| } |
|
|
| async function selectEncoding() { |
| try { |
| encodingInput.disabled = true; |
| fileInputButton.disabled = true; |
| await loadFiles(encodingInput.value); |
| } catch (error) { |
| alert(error); |
| } finally { |
| fileInputButton.disabled = false; |
| } |
| } |
|
|
| async function loadFiles(filenameEncoding) { |
| entries = await model.getEntries(selectedFile, { filenameEncoding }); |
| if (entries && entries.length) { |
| fileList.classList.remove("empty"); |
| const filenamesUTF8 = Boolean(!entries.find(entry => !entry.filenameUTF8)); |
| const encrypted = Boolean(entries.find(entry => entry.encrypted)); |
| encodingInput.value = filenamesUTF8 ? "utf-8" : filenameEncoding || "cp437"; |
| encodingInput.disabled = filenamesUTF8; |
| passwordInput.value = ""; |
| passwordInput.disabled = !encrypted; |
| refreshList(); |
| } |
| } |
|
|
| function refreshList() { |
| const newFileList = fileList.cloneNode(); |
| entries.forEach((entry, entryIndex) => { |
| const li = document.createElement("li"); |
| const filenameContainer = document.createElement("span"); |
| const filename = document.createElement("a"); |
| filenameContainer.classList.add("filename-container"); |
| li.appendChild(filenameContainer); |
| filename.classList.add("filename"); |
| filename.dataset.entryIndex = entryIndex; |
| filename.textContent = filename.title = entry.filename; |
| filename.title = `${entry.filename}\n Last modification date: ${entry.lastModDate.toLocaleString()}`; |
| if (!entry.directory) { |
| filename.href = ""; |
| filename.title += `\n Uncompressed size: ${entry.uncompressedSize.toLocaleString()} bytes`; |
| } |
| filenameContainer.appendChild(filename); |
| newFileList.appendChild(li); |
| }); |
| fileList.replaceWith(newFileList); |
| fileList = newFileList; |
| } |
|
|
| async function download(entry, li, a) { |
| if (!li.classList.contains("busy")) { |
| const unzipProgress = document.createElement("progress"); |
| li.appendChild(unzipProgress); |
| const controller = new AbortController(); |
| const signal = controller.signal; |
| const abortButton = document.createElement("button"); |
| abortButton.onclick = () => controller.abort(); |
| abortButton.textContent = "✖"; |
| abortButton.title = "Abort"; |
| li.querySelector(".filename-container").appendChild(abortButton); |
| li.classList.add("busy"); |
| li.onclick = event => event.preventDefault(); |
| try { |
| const blobURL = await model.getURL(entry, { |
| password: passwordInput.value, |
| onprogress: (index, max) => { |
| unzipProgress.value = index; |
| unzipProgress.max = max; |
| }, |
| signal |
| }); |
| a.href = blobURL; |
| a.download = entry.filename; |
| const clickEvent = new MouseEvent("click"); |
| a.dispatchEvent(clickEvent); |
| } catch (error) { |
| if (!signal.reason || signal.reason.code != error.code) { |
| throw error; |
| } |
| } finally { |
| li.classList.remove("busy"); |
| unzipProgress.remove(); |
| abortButton.remove(); |
| } |
| } |
| } |
|
|
| })(); |
|
|
| })(); |