MediaWiki

LiLiZip.js

Revision as of 11:27, 3 September 2024 by WikiLucas00 (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Note: After saving, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Go to Menu → Settings (Opera → Preferences on a Mac) and then to Privacy & security → Clear browsing data → Cached images and files.
/**
@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,
        languageData = [];

    // 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"),
        loadLanguageData()
    ).done(function() {
        librariesLoaded = true;
        console.log("Libraries loaded successfully.")
	    mw.loader.using(['jquery.ui'], function () {
            console.log("MediaWiki UI loaded. Initializing interface...");

	        // Check if we're on the tool page
	        if (mw.config.get('wgPageName') === 'LinguaLibre:LiLiZip/run') {
	            $(document).ready(function () {
	                createInterface();
	                addEventListeners();
	            });
	        }
	    });

    }).fail(function() {
        console.error("Failed to load required libraries. The tool may not function correctly.");
    });

	function loadLanguageData() {
	    return $.ajax({
	        url: "https://lingualibre.org/index.php?title=MediaWiki:LiLiZip.js/Data.js&action=raw&ctype=text/javascript",
	        dataType: "text",
	        success: function(data) {
	            var jsonStr = data.replace(/^\s*var\s+\w+\s*=\s*/, '').replace(/;?\s*$/, '');
	            languageData = JSON.parse(jsonStr);
	        },
	        error: function(xhr, status, error) {
	            console.error("Error loading language data:", status, error);
	        }
	    });
	}

    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: #eeeeee; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">
    <div id="iconContainer" style="margin-bottom: 20px;">
        <a href="https://lingualibre.org/wiki/LinguaLibre:LiLiZip">
            <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/d/d5/LiLiZip_icon.png/480px-LiLiZip_icon.png" alt="LiLiZip icon" style="width: 50%; border-radius: 10px;">
        </a>
    </div>
    <h1 style="font-weight: bold; color: #3366CC; margin-bottom: 20px;"><a href="https://lingualibre.org/wiki/LinguaLibre:LiLiZip">LiLiZip</a></h1>
    <div id="inputContainer" style="margin-bottom: 20px;">
        <select id="languageSelect" style="padding: 10px; border-radius: 5px; border: 1px solid #ccc; width: 100%; box-sizing: border-box; margin-bottom: 10px;">
            <option value="">Select a language</option>
            ${languageData.map(lang => `<option value="${lang.wikidata}|${lang.iso}">${lang.languageLabel}</option>`).join('')}
        </select>
        <div style="position: relative;">
            <input type="text" id="wordInput" placeholder="Enter a word to query" style="padding: 10px 30px 10px 10px; border-radius: 5px; border: 1px solid #ccc; width: 100%; box-sizing: border-box;">
        </div>
        <button id="submitWord" style="margin-top: 10px; 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">
<!-- Load Font Awesome -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" rel="stylesheet">

<!-- CSS -->
<style>
    body {background-color: #eeeeee;}
    #toolContainer {
        font-family: 'Charter', serif;
    }
</style>
    `;
        
        document.body.innerHTML = ''; // Clear existing content
        document.body.appendChild(container);
    }
		function handleInput() {
		    var languageSelect = document.getElementById('languageSelect');
		    var wordInput = document.getElementById('wordInput');
		    var submitButton = document.getElementById('submitWord');
		
		    if (languageSelect.value && wordInput.value.trim()) {
		        submitButton.disabled = false;
		    } else {
		        submitButton.disabled = true;
		    }
		}

	function addEventListeners() {
	    document.getElementById('languageSelect').addEventListener('change', handleInput);
	    document.getElementById('wordInput').addEventListener('input', handleInput);
	
	    document.getElementById('submitWord').addEventListener('click', function() {
	        var languageSelect = document.getElementById('languageSelect');
	        var wordInput = document.getElementById('wordInput').value.trim();
	        
	        if (languageSelect.value && wordInput) {
	            var [wikidata, iso] = languageSelect.value.split('|');
	            console.log("User submitted word:", wordInput, "for language:", wikidata, iso);
	            initDownload(wordInput, wikidata, iso);
	        } else {
	            console.log("Please select a language and enter a word.");
	        }
	    });
	
	    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, wikidata, iso, cont, callback) {
	    var params = {
	        action: "query",
	        format: "json",
	        prop: "imageinfo",
	        generator: "search",
	        utf8: 1,
	        iiprop: "url",
	        gsrsearch: `intitle:/LL-${wikidata} \\(${iso}\\)-[^-]*-${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);
	            callback([], null);
	        }
	    });
	}
    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, wikidata, iso) {
	    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-${wikidata}-${word}.zip`;
	    document.getElementById('inputContainer').style.display = 'none';
	    document.getElementById('progressContainer').style.display = 'block';
	
	    function fetchAndProcess(cont) {
	        var searchTerm = word;
	        if (/^Q\d+$/.test(word)) {
	            // If the input is a Wikidata QID, search for it in addition to the word
	            searchTerm = `${word}|${wikidata}`;
	        }
	        fetchQueryResults(searchTerm, wikidata, iso, 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();
	}
}(mediaWiki, jQuery));

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