MediaWiki

Difference between revisions of "LiLiZip.js"

(handle zero-result cases)
m
Line 1: Line 1:
 
/**
 
/**
@description: Manual mirror of https://commons.wikimedia.org/wiki/User:WikiLucas00/LinguaZip. Allows downloading files for a given word as a ZIP archive.
+
@description: JavaScript Gadget that allows downloading files for a given word of a given language as a ZIP archive.
 
@revision: 2024-08
 
@revision: 2024-08
 
@required modules: mediawiki.util, mediawiki.user, jquery.ui, ext.gadget.libCommons, ext.gadget.libJQuery, ext.gadget.libUtil, JSZip, FileSaver
 
@required modules: mediawiki.util, mediawiki.user, jquery.ui, ext.gadget.libCommons, ext.gadget.libJQuery, ext.gadget.libUtil, JSZip, FileSaver

Revision as of 12:11, 27 August 2024

/**
@description: JavaScript Gadget that allows downloading files for a given word of a given language as a ZIP archive.
@revision: 2024-08
@required modules: mediawiki.util, mediawiki.user, jquery.ui, ext.gadget.libCommons, ext.gadget.libJQuery, ext.gadget.libUtil, JSZip, FileSaver
* <nowiki>
**/
(function (mw, $) {
    "use strict";

    var downloadQueue = [],
        downloadCount = 0,
        zip,
        zipFileName,
        isCancelled = false,
        librariesLoaded = false;

    // Asynchronous loading of JSZip and FileSaver libraries
    $.when(
        $.getScript("https://tools-static.wmflabs.org/cdnjs/ajax/libs/jszip/3.10.1/jszip.min.js"),
        $.getScript("https://tools-static.wmflabs.org/cdnjs/ajax/libs/FileSaver.js/2.0.2/FileSaver.min.js")
    ).done(function() {
        librariesLoaded = true;
    });

    function createInterface() {
        var container = document.createElement('div');
        container.innerHTML = `
<div id="toolContainer" style="font-family: 'Charter', serif; max-width: 500px; margin: 50px auto; text-align: center; padding: 20px; border-radius: 10px; background-color: #fef6e7; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">
    <div id="iconContainer" style="margin-bottom: 20px;">
        <a href="https://commons.wikimedia.org/wiki/User:WikiLucas00/LinguaZip">
            <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/LinguaZip_icon.png/480px-LinguaZip_icon.png" alt="LinguaZip icon" style="width: 50%; border-radius: 10px;">
        </a>
    </div>
    <h1 style="font-weight: bold; color: #3366CC; margin-bottom: 20px;"><a href="https://commons.wikimedia.org/wiki/User:WikiLucas00/LinguaZip">LinguaZip.js</a></h1>
    <div id="inputContainer" style="margin-bottom: 20px;">
        <input type="text" id="wordInput" placeholder="Enter a word to query" style="padding: 10px; border-radius: 5px; border: 1px solid #ccc; width: 100%; box-sizing: border-box; margin-bottom: 10px;">
        <button id="submitWord" style="padding: 10px 20px; border-radius: 5px; background-color: #3366CC; color: white; border: none; cursor: pointer; width: 100%; font-size: 16px;">Submit</button>
    </div>
    <div id="progressContainer" style="display: none; margin-top: 20px;">
        <div id="progressBar" style="width: 100%; height: 25px; background-color: #f1f1f1; border-radius: 10px;">
            <div id="progressBarFill" style="width: 0%; height: 100%; background-color: #00af89; border-radius: 10px; transition: width 0.3s ease;"></div>
        </div>
        <p id="progressText" style="color: #333; margin-top: 10px;"></p>
        <button id="cancelDownload" style="margin-top: 15px; padding: 10px 20px; border-radius: 5px; background-color: #dd3333; color: white; border: none; cursor: pointer; width: 100%; font-size: 16px;">Cancel</button>
    </div>
    <footer style="margin-top: 30px; font-size: 14px; color: #333;">
        A tool from <a href="https://meta.wikimedia.org/wiki/User:WikiLucas00" target="_blank" style="color: #3366CC; text-decoration: none;">WikiLucas00</a>
    </footer>
</div>

<!-- Load Charter Font -->
<link href="https://fonts.googleapis.com/css2?family=Charter:wght@400;700&display=swap" rel="stylesheet">

<!-- CSS -->
<style>
    body {background-color: #fef6e7;}
    #toolContainer {
        font-family: 'Charter', serif;
    }
</style>

        `;
        
        document.body.innerHTML = ''; // Clear existing content
        document.body.appendChild(container);

        document.getElementById('submitWord').addEventListener('click', function() {
            var word = document.getElementById('wordInput').value.trim();
            if (word) {
                console.log("User submitted word:", word);
                initDownload(word);
            } else {
                console.log("No word entered, please try again.");
            }
        });

        document.getElementById('cancelDownload').addEventListener('click', function() {
            isCancelled = true;
            document.getElementById('progressContainer').style.display = 'none';
            document.getElementById('inputContainer').style.display = 'block';
            console.log("Download process cancelled by user.");
        });

        document.getElementById('wordInput').addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                document.getElementById('submitWord').click();
            }
        });
    }

    function resetUI() {
        var progressBarFill = document.getElementById('progressBarFill');
        var progressText = document.getElementById('progressText');
        if (progressBarFill && progressText) {
            progressBarFill.style.transition = 'none';
            progressBarFill.style.width = "0%";
            progressBarFill.style.backgroundColor = "green";
            progressText.textContent = "";
        }
        document.getElementById('progressContainer').style.display = 'none';
        document.getElementById('inputContainer').style.display = 'block';
    }

    function updateProgressBar(percent, processedCount, totalFiles) {
        var progressBarFill = document.getElementById('progressBarFill');
        var progressText = document.getElementById('progressText');
        if (progressBarFill && progressText) {
            progressBarFill.style.width = percent + "%";
            progressText.textContent = processedCount + "/" + totalFiles + " files downloaded";
        }
    }

    function smoothTransitionToBlueBar() {
        var progressBarFill = document.getElementById('progressBarFill');
        if (progressBarFill) {
            progressBarFill.style.transition = 'background-color 1s ease';
            progressBarFill.style.backgroundColor = "blue";
        }
    }

    function smoothIncrementTo100() {
        var currentWidth = parseFloat(document.getElementById('progressBarFill').style.width);
        if (currentWidth < 100) {
            setTimeout(() => {
                currentWidth += 1;
                updateProgressBar(currentWidth, downloadCount, downloadCount);
                if (currentWidth < 100) {
                    smoothIncrementTo100();
                }
            }, 10);
        }
    }

    function updateProgressBarForZipping() {
        smoothTransitionToBlueBar();
        var progressText = document.getElementById('progressText');
        if (progressText) {
            progressText.textContent = "Zipping files...";
        }
        smoothIncrementTo100();
    }

    function fetchQueryResults(word, cont, callback) {
        var params = {
            action: "query",
            format: "json",
            prop: "imageinfo",
            generator: "search",
            utf8: 1,
            iiprop: "url",
            gsrsearch: 'intitle:/LL-Q150 \\(fra\\)-[^-]*-' + word + '\\.wav/',
            gsrnamespace: 6,
            gsrlimit: "max",
            gsrwhat: "text",
            origin: "*"
        };

        if (cont) {
            params.continue = cont.continue;
            params.gsroffset = cont.gsroffset;
        }

        $.ajax({
            url: "https://commons.wikimedia.org/w/api.php",
            data: params,
            dataType: "json",
            success: function (data) {
                var files = [];
                if (data.query && data.query.pages) {
                    for (var pageId in data.query.pages) {
                        if (data.query.pages.hasOwnProperty(pageId)) {
                            var page = data.query.pages[pageId];
                            if (page.imageinfo && page.imageinfo.length > 0) {
                                files.push({
                                    title: page.title,
                                    url: page.imageinfo[0].url
                                });
                            }
                        }
                    }
                }
                callback(files, data.continue);
            },
            error: function (xhr, status, error) {
                console.error("API request failed:", status, error);
            }
        });
    }

    function processFilesInParallel(files) {
        const maxConcurrentDownloads = 100;
        let currentDownloads = 0;
        const maxRetries = 10; 

        function downloadFile(fileData, retryCount = 0) {

            fetch(fileData.url)
                .then(response => {
                    if (!response.ok) {
                        throw new Error("Network response was not ok for " + fileData.title);
                    }
                    return response.blob();
                })
                .then(blob => {
                    zip.file(fileData.title, blob, { binary: true });
                    currentDownloads--;
                    var processedCount = downloadCount - files.length - currentDownloads;
                    var percentComplete = (processedCount / downloadCount) * 100;
                    updateProgressBar(percentComplete, processedCount, downloadCount);
                    if (isCancelled) return;
                    downloadNextFile();
                })
                .catch(error => {
                    console.error("Error downloading file:", fileData.title, "; Error: ", error);
                    if (retryCount < maxRetries) {
                        console.log(`Retrying download for ${fileData.title} (Attempt ${retryCount + 1}/${maxRetries})`);
                        setTimeout(() => downloadFile(fileData, retryCount + 1), 1000); // Wait 1 second before retrying
                    } else {
                        console.error(`Failed to download ${fileData.title} after ${maxRetries} attempts`);
                        currentDownloads--;
                        if (isCancelled) return;
                        downloadNextFile();
                    }
                });
        }

        function downloadNextFile() {
            if (currentDownloads >= maxConcurrentDownloads || files.length === 0) {
                if (files.length === 0 && currentDownloads === 0 && !isCancelled) {
                    console.log("All files downloaded, starting zip process.");
                    updateProgressBarForZipping();
                    zip.generateAsync({ type: "blob" })
                        .then(function(content) {
                            saveAs(content, zipFileName);
                            resetUI();
                            console.log("Zip file saved as:", zipFileName);
                        });
                }
                return;
            }

            const fileData = files.shift();
            currentDownloads++;

            if (isCancelled) {
                console.log("Download process cancelled, exiting...");
                return;
            }

            downloadFile(fileData);
            downloadNextFile();
        }

        downloadNextFile();
    }

	function initDownload(word) {
	    if (!librariesLoaded) {
	        console.log("Libraries not loaded yet. Please try again in a moment.");
	        return;
	    }
	
	    resetUI();
	    downloadQueue = [];
	    downloadCount = 0;
	    zip = new JSZip();
	    isCancelled = false;
	    zipFileName = "lingualibre-Q150-" + word + ".zip";
	    document.getElementById('inputContainer').style.display = 'none';
	    document.getElementById('progressContainer').style.display = 'block';
	
	    function fetchAndProcess(cont) {
	        fetchQueryResults(word, cont, function (files, nextCont) {
	            if (files.length === 0 && !nextCont) {
	                // No results found
	                resetUI();
	                alert("No results found for the word: " + word);
	                return;
	            }
	
	            downloadQueue = downloadQueue.concat(files);
	            downloadCount += files.length;
	            updateProgressBar(0, 0, downloadCount);
	            processFilesInParallel(files);
	            if (nextCont) {
	                fetchAndProcess(nextCont);
	            } else if (downloadQueue.length === 0) {
	                // No results found after all pages have been checked
	                resetUI();
	                alert("No results found for the word: " + word);
	            }
	        });
	    }
	
	    fetchAndProcess();
	}

    // Run it all
    mw.loader.using(['jquery.ui'], function () {
        // Check if we're on the tool page
        if (mw.config.get('wgPageName') === 'LinguaLibre:LinguaZip/run') {
            $(document).ready(function () {
                createInterface();
            });
        }
    });

}(mediaWiki, jQuery));

// </nowiki>
// [[Category:LinguaZip]]