diff --git a/public/js/download.js b/public/js/download.js index b8d76fe..4041d6a 100644 --- a/public/js/download.js +++ b/public/js/download.js @@ -1,305 +1,233 @@ -$(() => { - $('#search-input').keypress(event => { - if (event.which === 13) { - search(); - } - }); - $('.filter-field').each((i, ele) => { - $(ele).change(() => { - buildTable(); - }); - }); -}); -const selectAll = () => { - $('tbody > tr').each((i, e) => { - $(e).addClass('ui-selected'); - }); -}; -const unselect = () => { - $('tbody > tr').each((i, e) => { - $(e).removeClass('ui-selected'); - }); -}; -const download = () => { - const selected = $('tbody > tr.ui-selected'); - if (selected.length === 0) return; - UIkit.modal.confirm(`Download ${selected.length} selected chapters?`).then(() => { - $('#download-btn').attr('hidden', ''); - $('#download-spinner').removeAttr('hidden'); - const ids = selected.map((i, e) => { - return $(e).find('td').first().text(); - }).get(); - const chapters = globalChapters.filter(c => ids.indexOf(c.id) >= 0); - console.log(ids); - $.ajax({ - type: 'POST', - url: base_url + 'api/admin/mangadex/download', - data: JSON.stringify({ - chapters: chapters - }), - contentType: "application/json", - dataType: 'json' - }) - .done(data => { - console.log(data); - if (data.error) { - alert('danger', `Failed to add chapters to the download queue. Error: ${data.error}`); +const downloadComponent = () => { + return { + chaptersLimit: 1000, + loading: false, + addingToDownload: false, + searchInput: '', + data: {}, + chapters: [], + langChoice: 'All', + groupChoice: 'All', + chapterRange: '', + volumeRange: '', + + get languages() { + const set = new Set(); + if (this.data.chapters) { + this.data.chapters.forEach(chp => { + set.add(chp.language); + }); + } + const ary = [...set].sort(); + ary.unshift('All'); + return ary; + }, + + get groups() { + const set = new Set(); + if (this.data.chapters) { + this.data.chapters.forEach(chp => { + Object.keys(chp.groups).forEach(g => { + set.add(g); + }); + }); + } + const ary = [...set].sort(); + ary.unshift('All'); + return ary; + }, + + init() { + const tableObserver = new MutationObserver(() => { + console.log('table mutated'); + $("#selectable").selectable({ + filter: 'tr' + }); + }); + tableObserver.observe($('table').get(0), { + childList: true, + subtree: true + }); + }, + filtersUpdated() { + if (!this.data.chapters) + this.chapters = []; + const filters = { + chapter: this.parseRange(this.chapterRange), + volume: this.parseRange(this.volumeRange), + lang: this.langChoice, + group: this.groupChoice + }; + console.log('filters:', filters); + let _chapters = this.data.chapters.slice(); + Object.entries(filters).forEach(([k, v]) => { + if (v === 'All') return; + if (k === 'group') { + _chapters = _chapters.filter(c => { + const unescaped_groups = Object.entries(c.groups).map(([g, id]) => this.unescapeHTML(g)); + return unescaped_groups.indexOf(v) >= 0; + }); return; } - const successCount = parseInt(data.success); - const failCount = parseInt(data.fail); - UIkit.modal.confirm(`${successCount} of ${successCount + failCount} chapters added to the download queue. Proceed to the download manager?`).then(() => { - window.location.href = base_url + 'admin/downloads'; + if (k === 'lang') { + _chapters = _chapters.filter(c => c.language === v); + return; + } + const lb = parseFloat(v[0]); + const ub = parseFloat(v[1]); + if (isNaN(lb) && isNaN(ub)) return; + _chapters = _chapters.filter(c => { + const val = parseFloat(c[k]); + if (isNaN(val)) return false; + if (isNaN(lb)) + return val <= ub; + else if (isNaN(ub)) + return val >= lb; + else + return val >= lb && val <= ub; }); - }) - .fail((jqXHR, status) => { - alert('danger', `Failed to add chapters to the download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`); - }) - .always(() => { - $('#download-spinner').attr('hidden', ''); - $('#download-btn').removeAttr('hidden'); }); - }); -}; -const toggleSpinner = () => { - var attr = $('#spinner').attr('hidden'); - if (attr) { - $('#spinner').removeAttr('hidden'); - $('#search-btn').attr('hidden', ''); - } else { - $('#search-btn').removeAttr('hidden'); - $('#spinner').attr('hidden', ''); - } - searching = !searching; -}; -var searching = false; -var globalChapters; -const search = () => { - if (searching) { - return; - } - $('#manga-details').attr('hidden', ''); - $('#filter-form').attr('hidden', ''); - $('table').attr('hidden', ''); - $('#selection-controls').attr('hidden', ''); - $('#filter-notification').attr('hidden', ''); - toggleSpinner(); - const input = $('input').val(); + console.log('filtered chapters:', _chapters); + this.chapters = _chapters; + }, + search() { + if (this.loading || this.searchInput === '') return; + this.loading = true; + this.data = {}; - if (input === "") { - toggleSpinner(); - return; - } - - var int_id = -1; - - try { - const path = new URL(input).pathname; - const match = /\/(?:title|manga)\/([0-9]+)/.exec(path); - int_id = parseInt(match[1]); - } catch (e) { - int_id = parseInt(input); - } - - if (int_id <= 0 || isNaN(int_id)) { - alert('danger', 'Please make sure you are using a valid manga ID or manga URL from Mangadex.'); - toggleSpinner(); - return; - } - - $.getJSON(`${base_url}api/admin/mangadex/manga/${int_id}`) - .done((data) => { - if (data.error) { - alert('danger', 'Failed to get manga info. Error: ' + data.error); + var int_id = -1; + try { + const path = new URL(this.searchInput).pathname; + const match = /\/(?:title|manga)\/([0-9]+)/.exec(path); + int_id = parseInt(match[1]); + } catch (e) { + int_id = parseInt(this.searchInput); + } + if (int_id <= 0 || isNaN(int_id)) { + alert('danger', 'Please make sure you are using a valid manga ID or manga URL from Mangadex.'); + this.loading = false; return; } - const cover = baseURL + data.cover_url; - $('#cover').attr("src", cover); - $('#title').text("Title: " + data.title); - $('#artist').text("Artist: " + data.artist); - $('#author').text("Author: " + data.author); + $.getJSON(`${base_url}api/admin/mangadex/manga/${int_id}`) + .done((data) => { + if (data.error) { + alert('danger', 'Failed to get manga info. Error: ' + data.error); + return; + } - $('#manga-details').removeAttr('hidden'); - - console.log(data.chapters); - globalChapters = data.chapters; - - let langs = new Set(); - let group_names = new Set(); - data.chapters.forEach(chp => { - Object.entries(chp.groups).forEach(([k, v]) => { - group_names.add(k); + this.data = data; + this.chapters = data.chapters; + }) + .fail((jqXHR, status) => { + alert('danger', `Failed to get manga info. Error: [${jqXHR.status}] ${jqXHR.statusText}`); + }) + .always(() => { + this.loading = false; }); - langs.add(chp.language); + + }, + + parseRange(str) { + const regex = /^[\t ]*(?:(?:(<|<=|>|>=)[\t ]*([0-9]+))|(?:([0-9]+))|(?:([0-9]+)[\t ]*-[\t ]*([0-9]+))|(?:[\t ]*))[\t ]*$/m; + const matches = str.match(regex); + var num; + + if (!matches) { + return [null, null]; + } else if (typeof matches[1] !== 'undefined' && typeof matches[2] !== 'undefined') { + // e.g., <= 30 + num = parseInt(matches[2]); + if (isNaN(num)) { + return [null, null]; + } + switch (matches[1]) { + case '<': + return [null, num - 1]; + case '<=': + return [null, num]; + case '>': + return [num + 1, null]; + case '>=': + return [num, null]; + } + } else if (typeof matches[3] !== 'undefined') { + // a single number + num = parseInt(matches[3]); + if (isNaN(num)) { + return [null, null]; + } + return [num, num]; + } else if (typeof matches[4] !== 'undefined' && typeof matches[5] !== 'undefined') { + // e.g., 10 - 23 + num = parseInt(matches[4]); + const n2 = parseInt(matches[5]); + if (isNaN(num) || isNaN(n2) || num > n2) { + return [null, null]; + } + return [num, n2]; + } else { + // empty or space only + return [null, null]; + } + }, + + unescapeHTML(str) { + var elt = document.createElement("span"); + elt.innerHTML = str; + return elt.innerText; + }, + + selectAll() { + $('tbody > tr').each((i, e) => { + $(e).addClass('ui-selected'); }); + }, - const comp = (a, b) => { - var ai; - var bi; - try { - ai = parseFloat(a); - } catch (e) {} - try { - bi = parseFloat(b); - } catch (e) {} - if (typeof ai === 'undefined') return -1; - if (typeof bi === 'undefined') return 1; - if (ai < bi) return 1; - if (ai > bi) return -1; - return 0; - }; - - langs = [...langs].sort(); - group_names = [...group_names].sort(); - - langs.unshift('All'); - group_names.unshift('All'); - - $('select#lang-select').html(langs.map(e => ``).join('')); - $('select#group-select').html(group_names.map(e => ``).join('')); - - $('#filter-form').removeAttr('hidden'); - - buildTable(); - }) - .fail((jqXHR, status) => { - alert('danger', `Failed to get manga info. Error: [${jqXHR.status}] ${jqXHR.statusText}`); - }) - .always(() => { - toggleSpinner(); - }); -}; -const parseRange = str => { - const regex = /^[\t ]*(?:(?:(<|<=|>|>=)[\t ]*([0-9]+))|(?:([0-9]+))|(?:([0-9]+)[\t ]*-[\t ]*([0-9]+))|(?:[\t ]*))[\t ]*$/m; - const matches = str.match(regex); - var num; - - if (!matches) { - alert('danger', `Failed to parse filter input ${str}`); - return [null, null]; - } else if (typeof matches[1] !== 'undefined' && typeof matches[2] !== 'undefined') { - // e.g., <= 30 - num = parseInt(matches[2]); - if (isNaN(num)) { - alert('danger', `Failed to parse filter input ${str}`); - return [null, null]; - } - switch (matches[1]) { - case '<': - return [null, num - 1]; - case '<=': - return [null, num]; - case '>': - return [num + 1, null]; - case '>=': - return [num, null]; - } - } else if (typeof matches[3] !== 'undefined') { - // a single number - num = parseInt(matches[3]); - if (isNaN(num)) { - alert('danger', `Failed to parse filter input ${str}`); - return [null, null]; - } - return [num, num]; - } else if (typeof matches[4] !== 'undefined' && typeof matches[5] !== 'undefined') { - // e.g., 10 - 23 - num = parseInt(matches[4]); - const n2 = parseInt(matches[5]); - if (isNaN(num) || isNaN(n2) || num > n2) { - alert('danger', `Failed to parse filter input ${str}`); - return [null, null]; - } - return [num, n2]; - } else { - // empty or space only - return [null, null]; - } -}; -const getFilters = () => { - const filters = {}; - $('.uk-select').each((i, ele) => { - const id = $(ele).attr('id'); - const by = id.split('-')[0]; - const choice = $(ele).val(); - filters[by] = choice; - }); - filters.volume = parseRange($('#volume-range').val()); - filters.chapter = parseRange($('#chapter-range').val()); - return filters; -}; -const buildTable = () => { - $('table').attr('hidden', ''); - $('#selection-controls').attr('hidden', ''); - $('#filter-notification').attr('hidden', ''); - console.log('rebuilding table'); - const filters = getFilters(); - console.log('filters:', filters); - var chapters = globalChapters.slice(); - Object.entries(filters).forEach(([k, v]) => { - if (v === 'All') return; - if (k === 'group') { - chapters = chapters.filter(c => { - const unescaped_groups = Object.entries(c.groups).map(([g, id]) => unescapeHTML(g)); - return unescaped_groups.indexOf(v) >= 0; + clearSelection() { + $('tbody > tr').each((i, e) => { + $(e).removeClass('ui-selected'); + }); + }, + + download() { + const selected = $('tbody > tr.ui-selected'); + if (selected.length === 0) return; + UIkit.modal.confirm(`Download ${selected.length} selected chapters?`).then(() => { + const ids = selected.map((i, e) => { + return parseInt($(e).find('td').first().text()); + }).get(); + const chapters = this.chapters.filter(c => ids.indexOf(c.id) >= 0); + console.log(ids); + this.addingToDownload = true; + $.ajax({ + type: 'POST', + url: `${base_url}api/admin/mangadex/download`, + data: JSON.stringify({ + chapters: chapters + }), + contentType: "application/json", + dataType: 'json' + }) + .done(data => { + console.log(data); + if (data.error) { + alert('danger', `Failed to add chapters to the download queue. Error: ${data.error}`); + return; + } + const successCount = parseInt(data.success); + const failCount = parseInt(data.fail); + UIkit.modal.confirm(`${successCount} of ${successCount + failCount} chapters added to the download queue. Proceed to the download manager?`).then(() => { + window.location.href = base_url + 'admin/downloads'; + }); + }) + .fail((jqXHR, status) => { + alert('danger', `Failed to add chapters to the download queue. Error: [${jqXHR.status}] ${jqXHR.statusText}`); + }) + .always(() => { + this.addingToDownload = false; + }); }); - return; } - if (k === 'lang') { - chapters = chapters.filter(c => c.language === v); - return; - } - const lb = parseFloat(v[0]); - const ub = parseFloat(v[1]); - if (isNaN(lb) && isNaN(ub)) return; - chapters = chapters.filter(c => { - const val = parseFloat(c[k]); - if (isNaN(val)) return false; - if (isNaN(lb)) - return val <= ub; - else if (isNaN(ub)) - return val >= lb; - else - return val >= lb && val <= ub; - }); - }); - console.log('filtered chapters:', chapters); - $('#count-text').text(`${chapters.length} chapters found`); - - const chaptersLimit = 1000; - if (chapters.length > chaptersLimit) { - $('#filter-notification').text(`Mango can only list ${chaptersLimit} chapters, but we found ${chapters.length} chapters in this manga. Please use the filter options above to narrow down your search.`); - $('#filter-notification').removeAttr('hidden'); - return; - } - - const inner = chapters.map(chp => { - const group_str = Object.entries(chp.groups).map(([k, v]) => { - return `${k}`; - }).join(' | '); - return `
Filter Chapters
- -ID | -Title | -Language | -Group | -Volume | -Chapter | -Timestamp | -
---|