// ==UserScript== // @name MusicBrainz: Batch-add "performance of" relationships // @description Batch link recordings to works from artist Recordings page. // @version 2018.2.18.1 // @author Michael Wiencek // @license X11 // @downloadURL https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/batch-add-recording-relationships.user.js // @updateURL https://raw.githubusercontent.com/murdos/musicbrainz-userscripts/master/batch-add-recording-relationships.user.js // @include *://musicbrainz.org/artist/*/recordings* // @include *://*.musicbrainz.org/artist/*/recordings* // @match *://musicbrainz.org/artist/*/recordings* // @match *://*.musicbrainz.org/artist/*/recordings* // ==/UserScript== // ==License== // Copyright (C) 2014 Michael Wiencek // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // // Except as contained in this notice, the name(s) of the above copyright // holders shall not be used in advertising or otherwise to promote the sale, // use or other dealings in this Software without prior written // authorization. // ==/License== var scr = document.createElement("script"); scr.textContent = "(" + batch_recording_rels + ")();"; document.body.appendChild(scr); function batch_recording_rels() { function setting(name) { name = 'bpr_' + name; if (arguments.length === 2) { localStorage.setItem(name, arguments[1]); } else { return localStorage.getItem(name); } } // 'leven' function taken from https://github.com/sindresorhus/leven // Copyright (c) Sindre Sorhus (sindresorhus.com) // Released under the MIT License: // https://raw.githubusercontent.com/sindresorhus/leven/49baddd/license function leven(a, b) { if (a === b) { return 0; } var aLen = a.length; var bLen = b.length; if (aLen === 0) { return bLen; } if (bLen === 0) { return aLen; } var bCharCode; var ret; var tmp; var tmp2; var i = 0; var j = 0; var arr = []; var charCodeCache = []; while (i < aLen) { charCodeCache[i] = a.charCodeAt(i); arr[i] = ++i; } while (j < bLen) { bCharCode = b.charCodeAt(j); tmp = j++; ret = j; for (i = 0; i < aLen; i++) { tmp2 = bCharCode === charCodeCache[i] ? tmp : tmp + 1; tmp = arr[i]; ret = arr[i] = tmp > ret ? tmp2 > ret ? ret + 1 : tmp2 : tmp2 > tmp ? tmp + 1 : tmp2; } } return ret; } // HTML helpers function make_element(el_name, args) { var el = $("<"+el_name+">"); el.append.apply(el, args); return el; } function td() { return make_element("td", arguments); } function tr() { return make_element("tr", arguments); } function table() { return make_element("table", arguments); } function label() { return make_element("label", arguments); } function goBtn(func) { return $("").click(func); } // Date parsing utils var dateRegex = /^(\d{4}|\?{4})(?:-(\d{2}|\?{2})(?:-(\d{2}|\?{2}))?)?$/; var integerRegex = /^[0-9]+$/; function parseInteger(num) { return integerRegex.test(num) ? parseInt(num, 10) : NaN; } function parseIntegerOrNull(str) { var integer = parseInteger(str); return isNaN(integer) ? null : integer; } function parseDate(str) { var match = str.match(dateRegex) || []; return { year: parseIntegerOrNull(match[1]), month: parseIntegerOrNull(match[2]), day: parseIntegerOrNull(match[3]) }; } function nonEmpty(value) { return value !== null && value !== undefined && value !== ''; } var daysInMonth = { "true": [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], "false": [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] }; function isDateValid(y, m, d) { y = nonEmpty(y) ? parseInteger(y) : null; m = nonEmpty(m) ? parseInteger(m) : null; d = nonEmpty(d) ? parseInteger(d) : null; if (isNaN(y) || isNaN(m) || isNaN(d)) return false; if (y !== null && y < 1) return false; if (m !== null && (m < 1 || m > 12)) return false; if (d === null) return true; var isLeapYear = y % 400 ? (y % 100 ? !(y % 4) : false) : true; if (d < 1 || d > 31 || d > daysInMonth[isLeapYear.toString()][m]) return false; return true; } // Request rate limiting var REQUEST_COUNT = 0; setInterval(function () { if (REQUEST_COUNT > 0) { REQUEST_COUNT -= 1; } }, 1000); function RequestManager(rate, count) { this.rate = rate; this.count = count; this.queue = []; this.last = 0; this.active = false; this.stopped = false; } RequestManager.prototype.next = function () { if (this.stopped || !this.queue.length) { this.active = false; return; } this.queue.shift()(); this.last = new Date().getTime(); REQUEST_COUNT += this.count; if (REQUEST_COUNT >= 10) { var diff = REQUEST_COUNT - 9; var timeout = diff * 1000; setTimeout(function (self) { self.next() }, this.rate + timeout, this); } else { setTimeout(function (self) { self.next() }, this.rate, this); } }; RequestManager.prototype.push_get = function (url, cb) { this.push(function () {$.get(url, cb);}); }; RequestManager.prototype.unshift_get = function (url, cb) { this.unshift(function () {$.get(url, cb);}); }; RequestManager.prototype.push = function (req) { this.queue.push(req); this.maybe_start_queue(); }; RequestManager.prototype.unshift = function (req) { this.queue.unshift(req); this.maybe_start_queue(); }; RequestManager.prototype.maybe_start_queue = function () { if (!(this.active || this.stopped)) { this.start_queue(); } }; RequestManager.prototype.start_queue = function () { if (this.active) { return; } this.active = true; this.stopped = false; var now = new Date().getTime(); if (now - this.last >= this.rate) { this.next(); } else { var timeout = this.rate - now + this.last; setTimeout(function (self) { self.next() }, timeout, this); } }; var ws_requests = new RequestManager(1000, 1); var edit_requests = new RequestManager(1500, 2); // Get recordings on the page var TITLE_SELECTOR = "a[href*='/recording/']"; var $recordings = $('tr:has(' + TITLE_SELECTOR + ')').data('filtered', false); if (!$recordings.length) { return; } var MBID_REGEX = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/; var WITHOUT_PAREN_CLAUSES_REGEX = /^(.+?)(?:(?: \([^()]+\))+)?$/; var ASCII_PUNCTUATION = [ [/…/g, "..."], [/‘/g, "'"], [/’/g, "'"], [/‚/g, "'"], [/“/g, "\""], [/”/g, "\""], [/„/g, "\""], [/′/g, "'"], [/″/g, "\""], [/‹/g, "<"], [/›/g, ">"], [/‐/g, "-"], [/‒/g, "-"], [/–/g, "-"], [/−/g, "-"], [/—/g, "-"], [/―/g, "--"] ]; function normalizeTitle(title) { title = title.toLowerCase().replace(/\s+/g, ''); _.each(ASCII_PUNCTUATION, function (val) { title = title.replace(val[0], val[1]); }); return title; } var RECORDING_TITLES = _.chain($recordings) .map(function (row) { var $title = $(row).find(TITLE_SELECTOR), mbid = $title.attr('href').match(MBID_REGEX)[0], norm_title = normalizeTitle($title.text().match(WITHOUT_PAREN_CLAUSES_REGEX)[1]); return [mbid, norm_title]; }) .fromPairs() .value(); var $work_options = _.chain(['type', 'language']) .map(function (kind) { return [kind, $('')]; }) .fromPairs() .value(); // Add button to manage performance ARs var $relate_table = table( tr(td(label("New work with this title:").attr('for',"bpr-new-work")), td('', goBtn(relate_to_new_titled_work))), tr(td(label("Existing work (URL/MBID):").attr('for',"bpr-existing-work")), td(entity_lookup('existing-work', "work"), goBtn(relate_to_existing_work))), tr(td("New works using recording titles"), td(goBtn(relate_to_new_works))), tr(td("Their suggested works"), td(goBtn(relate_to_suggested_works))), tr(td(label("Work type:").attr('for',"bpr-work-type")), td($work_options['type'])), tr(td(label("Lyrics language:").attr('for', "bpr-work-language")), td($work_options['language']))).hide(); var $works_table = table( $('').append( td(label("Load another artist’s works (URL/MBID):").attr('for', "bpr-load-artist")), td(entity_lookup('load-artist', "artist"), goBtn(load_artist_works_btn))) .hide()); var $container = table( tr(td("

Relate checked recordings to…

"), td("

Cached works

", $("(These are used to auto-suggest works.)") .css("font-size", "0.9em"))), tr(td($relate_table), td($works_table))) .css({"margin": "0.5em", "background": "#F2F2F2", "border": "1px #999 solid"}) .insertAfter($("div#content h2")[0]); var hide_performed_recs = setting('hide_performed_recs') === 'true' ? true : false; var hide_pending_edits = setting('hide_pending_edits') === 'true' ? true : false; function make_checkbox(func, default_val, lbl) { var chkbox = $('') .on("change", func) .attr("checked", default_val); return label(chkbox, lbl) } var $display_table = table( tr(td(label("Filter recordings list: ", $('').on("input", filter_recordings))), td(make_checkbox(toggle_performed_recordings, hide_performed_recs, "Hide recordings with performance ARs"), " ", make_checkbox(toggle_pending_edits, hide_pending_edits, "Hide recordings with pending edits")))) .css("margin", "0.5em") .insertAfter($container); var $recordings_load_msg = $("Loading performance relationships…"); $container.find("table").find("td").css("width", "auto"); $container.children("tbody").children("tr").children("td").css({ padding: "0.5em", "vertical-align": "top" }); // Get actual work types/languages ws_requests.unshift_get('/dialog?path=%2Fwork%2Fcreate', function (data) { var nodes = $.parseHTML(data); function populate($obj, kind) { $obj .append($('#id-edit-work\\.' + kind + '_id', nodes).children()) .val(setting('work_'+ kind) || 0) .on('change', function () { setting('work_' + kind, this.value); }); } _.each($work_options, populate); }); $("") .append(' ', $recordings_load_msg) .insertBefore($relate_table); // Add additional column $(".tbl > thead > tr").append("Performance Attributes"); var $date_element = $('') .attr('type', 'text') .attr('placeholder', 'yyyy-mm-dd') .addClass('date') .addClass('bpr-date-input') .css({ color : "#ddd", "width": "7em", border: "1px #999 solid" }); $recordings.append(td( $('part./' + 'live/' + 'inst./' + 'cover') .css("cursor", "pointer") .data("checked", false), ' ', $date_element).addClass("bpr_attrs")); $(document) .on('input', 'input.bpr-date-input', function () { var $input = $(this); $input.css("border-color", "#999"); if (this.value) { $input.css("color", "#000"); var parsedDate = parseDate(this.value); if ((parsedDate.year || parsedDate.month || parsedDate.day) && isDateValid(parsedDate.year, parsedDate.month, parsedDate.day)) { } else { $input.css("border-color", "#f00"); parsedDate = null; } $input.parent().data("date", parsedDate); } else { $input.css("color", "#ddd"); } }) .on('click', 'span.bpr-attr', function () { var $this = $(this); var checked = !$this.data('checked'); $this .data('checked', checked) .css({ background: checked ? 'blue': 'inherit', color: checked ? 'white' : 'black' }); }) // Style buttons function style_buttons($buttons) { return $buttons.css({ "color": "#565656", "background-color": "#FFFFFF", "border": "1px solid #D0D0D0", "border-top": "1px solid #EAEAEA", "border-left": "1px solid #EAEAEA"}); } style_buttons($container.find("button")); // Don't check hidden rows when the "select all" checkbox is pressed function uncheckRows($rows) { $rows.find("input[name=add-to-merge]").attr("checked", false); } $(".tbl > thead input[type=checkbox]") .on("change", function () { if (this.checked) { uncheckRows($recordings.filter(":hidden")); } }); var ARTIST_MBID = window.location.href.match(MBID_REGEX)[0]; var ARTIST_NAME = $("h1 a").text(); var $artist_works_msg = $(""); // Load performance relationships var CURRENT_PAGE = 1; var TOTAL_PAGES = 1; var page_numbers = $(".pagination .sel")[0]; var recordings_not_parsed = $recordings.length; if (page_numbers !== undefined) { CURRENT_PAGE = parseInt(page_numbers.href.match(/.+\?page=(\d+)/)[1] || "1", 10); var re_match = $("a[rel=xhv\\:last]:first").next("em").text().match(/Page \d+ of (\d+)/); TOTAL_PAGES = Math.ceil((re_match ? parseInt(re_match[1], 10) : 1) / 2); } var NAME_FILTER = $.trim($("#id-filter\\.name").val()); var ARTIST_FILTER = $.trim($("#id-filter\\.artist_credit_id").find("option:selected").text()); if (NAME_FILTER || ARTIST_FILTER) { get_filtered_page(0); } else { queue_recordings_request( "/ws/2/recording?artist=" + ARTIST_MBID + "&inc=work-rels" + "&limit=100" + "&offset=" + ((CURRENT_PAGE - 1) * 100) + "&fmt=json" ); } function request_recordings(url) { var attempts = 1; $.get(url, function (data) { var recs = data.recordings; var cache = {}; function extract_rec(node) { var row = cache[node.id]; if (row === undefined) { for (var j = 0; j < $recordings.length; j++) { var row_ = $recordings[j]; var row_id = $(row_).find(TITLE_SELECTOR).attr("href").match(MBID_REGEX)[0]; if (node.id === row_id) { row = row_; break; } else { cache[row_id] = row_; } } } if (row !== undefined) { parse_recording(node, $(row)); recordings_not_parsed -= 1; } } if (recs) { _.each(recs, extract_rec); } else { extract_rec(data); } if (hide_performed_recs) { $recordings.filter(".performed").hide(); restripeRows(); } }) .done(function () { $recordings_load_msg.parent().remove(); $relate_table.show(); load_works_init(); }) .fail(function () { $recordings_load_msg .text("Error loading relationships. Retry #" + attempts + "...") .css("color", "red"); attempts += 1; ws_requests.unshift(request_recordings); }); } function queue_recordings_request(url) { ws_requests.push(function () { request_recordings(url); }); } function get_filtered_page(page) { var url = ( "/ws/2/recording?query=" + (NAME_FILTER ? encodeURIComponent(NAME_FILTER) + "%20AND%20" : "") + (ARTIST_FILTER ? "creditname:" + encodeURIComponent(ARTIST_FILTER) + "%20AND%20" : "") + " arid:" + ARTIST_MBID + "&limit=100" + "&offset=" + (page * 100) + "&fmt=json" ); ws_requests.push_get(url, function (data) { _.each(data.recordings, function (r) { queue_recordings_request("/ws/2/recording/" + r.id + "?inc=work-rels&fmt=json"); }); if (recordings_not_parsed > 0 && page < TOTAL_PAGES - 1) { get_filtered_page(page + 1); } }); } function parse_recording(node, $row) { var $attrs = $row.children("td.bpr_attrs"); var performed = false; $row.data("performances", []); $attrs.data("checked", false).css("color", "black"); _.each(node.relations, function (rel) { if (rel.type.match(/performance/)) { if (!performed) { $row.addClass("performed"); performed = true; } if (rel.begin) { $attrs.find("input.date").val(rel.begin).trigger("input"); } var attrs = []; _.each(rel.attributes, function (name) { var cannonical_name = name.toLowerCase(); var $button = $attrs.find("span." + cannonical_name); attrs.push(cannonical_name); if (!$button.data("checked")) { $button.click(); } }); add_work_link($row, rel.work.id, rel.work.title, rel.work.disambiguation, attrs); $row.data("performances").push(rel.work.id); } }); //Use the dates in "live YYYY-MM-DD" disambiguation comments var comment = node.disambiguation; var date = comment && comment.match && comment.match(/live(?: .+)?, ([0-9]{4}(?:-[0-9]{2}(?:-[0-9]{2})?)?)(?:\: .+)?$/); if (date) { $attrs.find("input.date").val(date[1]).trigger("input"); } if (!performed) { if (node.title.match(/.+\(live.*\)/) || (comment && comment.match && comment.match(/^live.*/))) { $attrs.find("span.live").click(); } else { var url = "/ws/2/recording/" + node.id + "?inc=releases+release-groups&fmt=json"; var request_rec = function () { $.get(url, function (data) { var releases = data.releases; for (var i = 0; i < releases.length; i++) { if (_.includes(releases[i]["release-group"]["secondary-types"], "Live")) { $attrs.find("span.live").click(); break; } } }).fail(function () { ws_requests.push(request_rec); }); } ws_requests.push(request_rec); } } } // Load works var WORKS_LOAD_CACHE = []; var LOADED_WORKS = {}; var LOADED_ARTISTS = {}; function load_works_init() { var artists_string = localStorage.getItem("bpr_artists " + ARTIST_MBID); var artists = []; if (artists_string) { artists = artists_string.split("\n"); } function callback() { if (artists.length > 0) { var parts = artists.pop(); var mbid = parts.slice(0, 36); var name = parts.slice(36); if (mbid && name) { load_artist_works(mbid, name).done(callback); } } } load_artist_works(ARTIST_MBID, ARTIST_NAME).done(callback); } function load_artist_works(mbid, name) { var deferred = $.Deferred(); if (LOADED_ARTISTS[mbid]) { return deferred.promise(); } LOADED_ARTISTS[mbid] = true; var $table_row = $(""); var $button_cell = $("").css("display", "none"); var $msg = $artist_works_msg; if (mbid !== ARTIST_MBID) { $msg = $(""); $button_cell.append( style_buttons($("")) .click(function () { $table_row.remove(); remove_artist_works(mbid); })); } var $reload = style_buttons($("")) .click(function () { $button_cell.css("display", "none"); $msg.text("Loading works for " + name + "..."); load(); }) .prependTo($button_cell); $msg.text("Loading works for " + name + "...").css("color", "green"), $table_row.append($msg, $button_cell); $("tr#bpr-works-row").css("display", "none").before($table_row); var works_date = localStorage.getItem("bpr_works_date " + mbid); var result = []; function finished(result) { var parsed = load_works_finish(result); update_artist_works_msg($msg, result.length, name, works_date); $button_cell.css("display", "table-cell"); $("tr#bpr-works-row").css("display", "table-row"); deferred.resolve(); match_works(parsed[0], parsed[1], parsed[2], parsed[3]); } if (works_date) { var works_string = localStorage.getItem("bpr_works " + mbid); if (works_string) { finished(works_string.split("\n")); return deferred.promise(); } } load(); function load() { works_date = new Date().toString(); localStorage.setItem("bpr_works_date " + mbid, works_date); result = []; var callback = function (loaded, remaining) { result.push.apply(result, loaded); if (remaining > 0) { $msg.text("Loading " + remaining.toString() + " works for " + name + "..."); } else { localStorage.setItem("bpr_works " + mbid, result.join("\n")); finished(result); } }; var works_url = "/ws/2/work?artist=" + mbid + "&inc=aliases&limit=100&fmt=json"; ws_requests.unshift(function () { request_works(works_url, 0, -1, callback); }); } return deferred.promise(); } function load_works_finish(result) { var tmp_mbids = []; var tmp_titles = []; var tmp_comments = []; var tmp_norm_titles = []; _.each(result, function (parts) { var mbid = parts.slice(0, 36); var rest = parts.slice(36).split("\u00a0"); LOADED_WORKS[mbid] = true; tmp_mbids.push(mbid); tmp_titles.push(rest[0]); tmp_comments.push(rest[1] || ""); tmp_norm_titles.push(normalizeTitle(rest[0])); }); return [tmp_mbids, tmp_titles, tmp_comments, tmp_norm_titles]; } function request_works(url, offset, count, callback) { $.get(url + "&offset=" + offset, function (data, textStatus, jqXHR) { if (count < 0) { count = data['work-count']; } var works = data.works; var loaded = []; _.each(works, function (work) { var comment = work.disambiguation; loaded.push(work.id + work.title + (comment ? "\u00a0" + comment : "")); }); callback(loaded, count - offset - works.length); if (works.length + offset < count) { ws_requests.unshift(function () { request_works(url, offset + 100, count, callback); }); } }).fail(function () { ws_requests.unshift(function () { request_works(url, offset, count, callback); }); }); } function match_works(mbids, titles, comments, norm_titles) { if (!mbids.length) { return; } var $not_performed = $recordings.filter(":not(.performed)"); if (!$not_performed.length) { return; } function sim(r, w) { r = r || ''; w = w || ''; return r == w ? 0 : leven(r, w) / ((r.length + w.length) / 2); } var matches = {}; var to_recording = function ($rec, rec_title) { if (rec_title in matches) { var match = matches[rec_title]; suggested_work_link($rec, match[0], match[1], match[2]); return; } var $progress = $(""); rowTitleCell($rec).append( $('
').append( $("Looking for matching work…"), " ", $progress) .css({"font-size": "0.9em", "padding": "0.3em", "padding-left": "1em", "color": "orange"})); var current = 0; var context = { minScore: 0.250001, match: null }; var total = mbids.length; var done = function () { var match = context.match; if (match !== null) { matches[rec_title] = match; suggested_work_link($rec, match[0], match[1], match[2]); } else { $progress.parent().remove(); } }; var iid = setInterval(function () { var j = current++; var norm_work_title = norm_titles[j]; var score = sim(rec_title, norm_work_title); if (current % 12 === 0) { $progress.text(current.toString() + "/" + total.toString()); } if (score < context.minScore) { context.match = [mbids[j], titles[j], comments[j]]; if (score === 0) { clearInterval(iid); done(); return; } context.minScore = score; } if (j === total - 1) { clearInterval(iid); done(); } }, 0); }; for (var i = 0; i < $not_performed.length; i++) { var $rec = $not_performed.eq(i); var mbid = $rec.find(TITLE_SELECTOR).attr("href").match(MBID_REGEX)[0]; to_recording($rec, RECORDING_TITLES[mbid]); } } function suggested_work_link($rec, mbid, title, comment) { var $title_cell = rowTitleCell($rec); $title_cell.children("div.suggested-work").remove(); $title_cell.append( $('
').append( $("Suggested work:").css({"color": "green", "font-weight": "bold"}), " ", $("") .attr("href", "/work/" + mbid) .text(title), (comment ? " " : null), (comment ? $("").text("(" + comment + ")") : null)) .css({"font-size": "0.9em", "padding": "0.3em", "padding-left": "1em"})); $rec.data("suggested_work_mbid", mbid); $rec.data("suggested_work_title", title); } function remove_artist_works(mbid) { if (!LOADED_ARTISTS[mbid]) { return; } delete LOADED_ARTISTS[mbid]; var item_key = "bpr_artists " + ARTIST_MBID; localStorage.setItem( item_key, _.filter(localStorage.getItem(item_key).split("\n"), function (artist) { return artist.slice(0, 36) !== mbid; }) .join("\n")); } function cache_work(mbid, title, comment) { LOADED_WORKS[mbid] = true; WORKS_LOAD_CACHE.push(mbid + title + (comment ? "\u00a0" + comment : "")); var norm_title = normalizeTitle(title); var works_date = localStorage.getItem("bpr_works_date " + ARTIST_MBID); var count = $artist_works_msg.data("works_count") + 1; update_artist_works_msg($artist_works_msg, count, ARTIST_NAME, works_date); match_works([mbid], [title], [comment], [norm_title]); } function flush_work_cache() { if (!WORKS_LOAD_CACHE.length) { return; } var works_string = localStorage.getItem("bpr_works " + ARTIST_MBID); if (works_string) { works_string += "\n" + WORKS_LOAD_CACHE.join("\n"); } else { works_string = WORKS_LOAD_CACHE.join("\n"); } localStorage.setItem("bpr_works " + ARTIST_MBID, works_string); WORKS_LOAD_CACHE = []; } function load_artist_works_btn() { var $input = $("#bpr-load-artist"); if (!$input.data("selected")) { return; } var mbid = $input.data("mbid"); var name = $input.data("name"); load_artist_works(mbid, name).done(function () { var artists_string = localStorage.getItem("bpr_artists " + ARTIST_MBID); if (artists_string) { artists_string += "\n" + mbid + name; } else { artists_string = mbid + name; } localStorage.setItem("bpr_artists " + ARTIST_MBID, artists_string); }); } function update_artist_works_msg($msg, count, name, works_date) { $msg .html("") .append( count + " works loaded for " + name + "
", $('(cached ' + works_date + ')').css({"font-size": "0.8em"}) ) .data("works_count", count); } // Edit creation function relate_all_to_work(mbid, title, comment) { var deferred = $.Deferred(); var $rows = checked_recordings(); var total = $rows.length; if (!total) { deferred.resolve(); return deferred.promise(); } for (var i = 0; i < total; i++) { var $row = $rows.eq(i); $row.children("td").not(":has(input)").first() .css("color", "LightSlateGray") .find("a").css("color", "LightSlateGray"); var promise = relate_to_work($row, mbid, title, comment, false, false); if (i === total - 1) { promise.done(function () { deferred.resolve() }); } } if (!LOADED_WORKS[mbid]) { cache_work(mbid, title, comment); flush_work_cache(); } return deferred.promise(); } function relate_to_new_titled_work() { var $rows = checked_recordings(); var total = $rows.length; var title = $("#bpr-new-work").val(); if (!total || !title) { return; } ws_requests.stopped = true; var $button = $(this).attr("disabled", true).css("color", "#EAEAEA"); function callback() { ws_requests.stopped = false; ws_requests.start_queue(); $button.attr("disabled", false).css("color", "#565656"); } create_new_work(title, function (data) { var work = data.match(/\/work\/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/); relate_all_to_work(work[1], title, "").done(callback) }); } function relate_to_existing_work() { var $input = $("input#bpr-existing-work"); var $button = $(this); function callback() { ws_requests.stopped = false; ws_requests.start_queue(); $button.attr("disabled", false).css("color", "#565656"); } if ($input.data("selected")) { ws_requests.stopped = true; $button.attr("disabled", true).css("color", "#EAEAEA"); relate_all_to_work( $input.data("mbid"), $input.data("name"), $input.data("comment") || "" ) .done(callback); } else { $input.css("background", "#ffaaaa"); } } function relate_to_new_works() { var $rows = checked_recordings(); var total_rows = $rows.length; if (!total_rows) { return; } ws_requests.stopped = true; var $button = $(this) .attr("disabled", true) .css("color", "#EAEAEA"); $.each($rows, function (i, row) { var $row = $(row); var $title_cell = rowTitleCell($row); var title = $title_cell.find(TITLE_SELECTOR).text(); $title_cell.css("color", "LightSlateGray").find("a").css("color", "LightSlateGray"); create_new_work(title, function (data) { var work = data.match(/\/work\/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/); var promise = relate_to_work($row, work[1], title, "", true, true); if (--total_rows === 0) { promise.done(function () { flush_work_cache(); ws_requests.stopped = false; ws_requests.start_queue(); $button.attr("disabled", false).css("color", "#565656"); }); } }); }); } function create_new_work(title, callback) { function post_edit() { var data = "edit-work.name=" + title; _.each($work_options, function($obj, kind) { if ($obj.val()) { data += "&edit-work." + kind + "_id=" + $obj.val(); } }); $.post("/work/create", data, callback).fail(function () { edit_requests.unshift(post_edit); }); } edit_requests.push(post_edit); } function relate_to_suggested_works() { var $rows = checked_recordings().filter(function () { return $(this).data("suggested_work_mbid"); }); var total = $rows.length; if (!total) { return; } var $button = $(this).attr("disabled", true).css("color", "#EAEAEA"); ws_requests.stopped = true; function callback() { ws_requests.stopped = false; ws_requests.start_queue(); $button.attr("disabled", false).css("color", "#565656"); }; $.each($rows, function (i, row) { var $row = $(row); var mbid = $row.data("suggested_work_mbid"); var title = $row.data("suggested_work_title"); var $title_cell = rowTitleCell($row); $title_cell.css("color", "LightSlateGray").find("a").css("color", "LightSlateGray"); var promise = relate_to_work($row, mbid, title, "", false, false); if (i === total - 1) { promise.done(callback); } }); } function add_work_link($row, mbid, title, comment, attrs) { var $title_cell = rowTitleCell($row); $title_cell.children("div.suggested-work").remove(); $row.removeData("suggested_work_mbid").removeData("suggested_work_title"); $title_cell .removeAttr("style") .append($('
') .text(attrs.join(' ') + " recording of ") .css({"font-size": "0.9em", "padding": "0.3em", "padding-left": "1em"}) .append($("").attr("href", "/work/" + mbid).text(title), (comment ? " " : null), (comment ? $("").text("(" + comment + ")") : null))); } function relate_to_work($row, work_mbid, work_title, work_comment, check_loaded, priority) { var deferred = $.Deferred(); var performances = $row.data("performances"); if (performances) { if (performances.indexOf(work_mbid) === -1) { performances.push(work_mbid); } else { deferred.resolve(); return deferred.promise(); } } else { $row.data("performances", [work_mbid]); } var rec_mbid = $row.find(TITLE_SELECTOR).attr("href").match(MBID_REGEX)[0]; var $title_cell = rowTitleCell($row); var title_link = $title_cell.children("a")[0]; var $attrs = $row.children("td.bpr_attrs"); var selectedAttrs = []; function selected(attr) { var checked = $attrs.children("span." + attr).data("checked") ? 1 : 0; if (checked) { selectedAttrs.push(attr); } return checked; } var data = { "rel-editor.rels.0.action": "add", "rel-editor.rels.0.link_type": "278", "rel-editor.rels.0.entity.1.type": "work", "rel-editor.rels.0.entity.1.gid": work_mbid, "rel-editor.rels.0.entity.0.type": "recording", "rel-editor.rels.0.entity.0.gid": rec_mbid }; var attrs = []; if (selected("live")) attrs.push("70007db6-a8bc-46d7-a770-80e6a0bb551a"); if (selected("partial")) attrs.push("d2b63be6-91ec-426a-987a-30b47f8aae2d"); if (selected("instrumental")) attrs.push("c031ed4f-c9bb-4394-8cf5-e8ce4db512ae"); if (selected("cover")) attrs.push("1e8536bd-6eda-3822-8e78-1c0f4d3d2113"); _.each(attrs, function (attr, index) { data["rel-editor.rels.0.attributes." + index + ".type.gid"] = attr; }); var date = $attrs.data("date"); if (date != null) { data["rel-editor.rels.0.period.begin_date.year"] = date["year"]; data["rel-editor.rels.0.period.begin_date.month"] = date["month"] || ""; data["rel-editor.rels.0.period.begin_date.day"] = date["day"] || ""; data["rel-editor.rels.0.period.end_date.year"] = date["year"]; data["rel-editor.rels.0.period.end_date.month"] = date["month"] || ""; data["rel-editor.rels.0.period.end_date.day"] = date["day"] || ""; } function post_edit() { $(title_link).css("color", "green"); $.post('/relationship-editor', data, function () { add_work_link($row, work_mbid, work_title, work_comment, selectedAttrs); $(title_link).removeAttr("style"); $row.addClass("performed"); if (hide_performed_recs) { uncheckRows($row.hide()); restripeRows(); } deferred.resolve(); }).fail(function () { edit_requests.unshift(post_edit); }); } if (priority) { edit_requests.unshift(post_edit); } else { edit_requests.push(post_edit); } if (check_loaded) { if (!LOADED_WORKS[work_mbid]) { cache_work(work_mbid, work_title, work_comment); } } return deferred.promise(); } function filter_recordings() { var string = this.value.toLowerCase(); for (var i = 0; i < $recordings.length; i++) { var $rec = $recordings.eq(i); var title = $rec.find(TITLE_SELECTOR).text().toLowerCase(); if (title.indexOf(string) !== -1) { $rec.data("filtered", false); if (!hide_performed_recs || !$rec.hasClass("performed")) { $rec.show(); } } else { $rec.hide().data("filtered", true); } } restripeRows(); } function toggle_performed_recordings() { var $performed = $recordings.filter(".performed"); hide_performed_recs = this.checked; if (hide_performed_recs) { uncheckRows($performed.hide()); } else { $performed.filter(function () { return !$(this).data("filtered") }).show(); } restripeRows(); setting('hide_performed_recs', hide_performed_recs.toString()); } function toggle_pending_edits(event, checked) { var $pending = $recordings.filter(function () { return $(this).find(TITLE_SELECTOR).parent().parent().is("span.mp"); }); hide_pending_edits = checked !== undefined ? checked : this.checked; if (hide_pending_edits) { uncheckRows($pending.hide()); } else { $pending.filter(function () { return !$(this).data("filtered") }).show(); } restripeRows(); setting('hide_pending_edits', hide_pending_edits.toString()); } toggle_pending_edits(null, hide_pending_edits); function checked_recordings() { return $recordings .filter(":visible") .filter(function () { return $(this).find("input[name=add-to-merge]:checked").length }); } function entity_lookup(id_suffix, entity) { var $input = $('') $input.on("input", function () { var match = this.value.match(MBID_REGEX); $(this).data("selected", false); if (match) { var mbid = match[0]; ws_requests.unshift_get("/ws/2/" + entity + "/" + mbid + "?fmt=json", function (data) { var value = data.title || data.name; var out_data = {"selected": true, "mbid": mbid, "name": value}; if (entity === "work" && data.disambiguation) { out_data.comment = data.disambiguation; } $input.val(value).data(out_data).css("background", "#bbffbb"); }).fail(function () { $input.css("background", "#ffaaaa"); }); } else { $input.css("background", "#ffaaaa"); } }).data("selected", false); return $input; } function restripeRows() { $recordings.filter(":visible").each(function (index, row) { var even = (index + 1) % 2 === 0; row.className = row.className.replace(even ? 'odd' : 'even', even ? 'even' : 'odd'); }); } function rowTitleCell($row) { return $row.children('td:has(' + TITLE_SELECTOR + ')'); } }