musicbrainz-userscripts/batch-add-recording-relationships.user.js

1341 lines
45 KiB
JavaScript
Raw Normal View History

// ==UserScript==
// @name MusicBrainz: Batch-add "performance of" relationships
2017-03-10 18:50:20 +00:00
// @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() {
2014-12-29 04:40:23 +00:00
2015-08-21 22:56:42 +00:00
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@gmail.com> (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;
}
2014-12-29 05:51:11 +00:00
// HTML helpers
2014-12-29 04:40:23 +00:00
function make_element(el_name, args) {
var el = $("<"+el_name+"></"+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);
}
2014-12-29 04:40:23 +00:00
function goBtn(func) {
return $("<button>Go</button>").click(func);
}
2015-05-26 19:35:51 +00:00
// 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;
}
2014-12-29 05:51:11 +00:00
// 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;
2014-12-29 05:52:47 +00:00
setTimeout(function (self) { self.next() }, this.rate + timeout, this);
2014-12-29 05:51:11 +00:00
} else {
2014-12-29 05:52:47 +00:00
setTimeout(function (self) { self.next() }, this.rate, this);
2014-12-29 05:51:11 +00:00
}
};
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);});
};
2014-12-29 05:51:11 +00:00
RequestManager.prototype.push = function (req) {
this.queue.push(req);
this.maybe_start_queue();
2014-12-29 05:51:11 +00:00
};
RequestManager.prototype.unshift = function (req) {
this.queue.unshift(req);
this.maybe_start_queue();
};
RequestManager.prototype.maybe_start_queue = function () {
2014-12-29 05:51:11 +00:00
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;
2014-12-29 05:52:47 +00:00
setTimeout(function (self) { self.next() }, timeout, this);
2014-12-29 05:51:11 +00:00
}
};
var ws_requests = new RequestManager(1000, 1);
var edit_requests = new RequestManager(1500, 2);
// Get recordings on the page
2014-12-29 03:53:17 +00:00
2016-05-23 21:35:20 +00:00
var TITLE_SELECTOR = "a[href*='/recording/']";
2014-12-29 04:18:07 +00:00
var $recordings = $('tr:has(' + TITLE_SELECTOR + ')').data('filtered', false);
2014-12-29 05:51:11 +00:00
2014-12-29 03:53:17 +00:00
if (!$recordings.length) {
return;
2014-12-29 03:53:17 +00:00
}
2014-12-29 03:53:17 +00:00
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 = /^(.+?)(?:(?: \([^()]+\))+)?$/;
2014-12-29 03:53:17 +00:00
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]);
});
2014-12-29 03:53:17 +00:00
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]);
2014-12-29 03:53:17 +00:00
return [mbid, norm_title];
})
.fromPairs()
.value();
var $work_options = _.chain(['type', 'language'])
.map(function (kind) { return [kind, $('<select id="bpr-work-' + kind + '"></select>')]; })
.fromPairs()
.value();
// Add button to manage performance ARs
2014-12-29 04:40:23 +00:00
var $relate_table = table(
tr(td(label("New work with this title:").attr('for',"bpr-new-work")),
2014-12-29 04:40:23 +00:00
td('<input type="text" id="bpr-new-work"/>',
goBtn(relate_to_new_titled_work))),
tr(td(label("Existing work (URL/MBID):").attr('for',"bpr-existing-work")),
2015-01-02 01:37:08 +00:00
td(entity_lookup('existing-work', "work"),
2014-12-29 04:40:23 +00:00
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();
2014-12-29 04:40:23 +00:00
var $works_table = table(
$('<tr id="bpr-works-row"></tr>').append(
td(label("Load another artists works (URL/MBID):").attr('for', "bpr-load-artist")),
2015-01-02 01:37:08 +00:00
td(entity_lookup('load-artist', "artist"),
2014-12-29 04:40:23 +00:00
goBtn(load_artist_works_btn)))
.hide());
2014-12-29 04:40:23 +00:00
var $container = table(
tr(td("<h3>Relate checked recordings to…</h3>"),
td("<h3>Cached works</h3>",
$("<span>(These are used to auto-suggest works.)</span>")
.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]);
2015-08-21 22:56:42 +00:00
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 = $('<input type="checkbox"/>')
2014-12-29 08:05:25 +00:00
.on("change", func)
.attr("checked", default_val);
return label(chkbox, lbl)
}
2014-12-29 04:40:23 +00:00
var $display_table = table(
tr(td(label("Filter recordings list: ",
2014-12-29 08:05:25 +00:00
$('<input type="text"/>').on("input", filter_recordings))),
td(make_checkbox(toggle_performed_recordings, hide_performed_recs,
"Hide recordings with performance ARs"),
2014-12-29 04:40:23 +00:00
"&#160;",
make_checkbox(toggle_pending_edits, hide_pending_edits,
"Hide recordings with pending edits"))))
.css("margin", "0.5em")
.insertAfter($container);
var $recordings_load_msg = $("<span>Loading performance relationships…</span>");
2015-01-02 04:30:05 +00:00
$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())
2015-08-21 22:56:42 +00:00
.val(setting('work_'+ kind) || 0)
2015-01-02 04:30:05 +00:00
.on('change', function () {
2015-08-21 22:56:42 +00:00
setting('work_' + kind, this.value);
2015-01-02 04:30:05 +00:00
});
}
_.each($work_options, populate);
});
$("<span></span>")
.append('<img src="/static/images/icons/loading.gif"/> ', $recordings_load_msg)
.insertBefore($relate_table);
// Add additional column
$(".tbl > thead > tr").append("<th>Performance Attributes</th>");
2014-12-29 07:32:12 +00:00
var $date_element = $('<input />')
.attr('type', 'text')
2014-12-29 07:34:38 +00:00
.attr('placeholder', 'yyyy-mm-dd')
.addClass('date')
2014-12-29 07:32:12 +00:00
.addClass('bpr-date-input')
.css({ color : "#ddd", "width": "7em", border: "1px #999 solid" });
$recordings.append(td(
$('<span class="bpr-attr partial">part.</span>/' +
'<span class="bpr-attr live">live</span>/' +
'<span class="bpr-attr instrumental">inst.</span>/' +
'<span class="bpr-attr cover">cover</span>')
.css("cursor", "pointer")
.data("checked", false),
'&#160;',
$date_element).addClass("bpr_attrs"));
$(document)
.on('input', 'input.bpr-date-input', function () {
var $input = $(this);
2015-01-02 04:30:05 +00:00
$input.css("border-color", "#999");
if (this.value) {
2015-01-02 04:30:05 +00:00
$input.css("color", "#000");
2015-05-26 19:35:51 +00:00
var parsedDate = parseDate(this.value);
2015-01-02 04:30:05 +00:00
if ((parsedDate.year || parsedDate.month || parsedDate.day) &&
2015-05-26 19:35:51 +00:00
isDateValid(parsedDate.year, parsedDate.month, parsedDate.day)) {
} else {
2015-01-02 04:30:05 +00:00
$input.css("border-color", "#f00");
parsedDate = null;
}
2015-01-02 04:30:05 +00:00
$input.parent().data("date", parsedDate);
2015-01-02 04:34:21 +00:00
} else {
$input.css("color", "#ddd");
}
2014-12-29 07:32:12 +00:00
})
.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
2014-12-29 08:05:25 +00:00
function uncheckRows($rows) {
$rows.find("input[name=add-to-merge]").attr("checked", false);
}
$(".tbl > thead input[type=checkbox]")
2014-12-29 08:05:25 +00:00
.on("change", function () {
if (this.checked) {
2014-12-29 08:05:25 +00:00
uncheckRows($recordings.filter(":hidden"));
}
});
2014-12-29 06:02:18 +00:00
var ARTIST_MBID = window.location.href.match(MBID_REGEX)[0];
var ARTIST_NAME = $("h1 a").text();
var $artist_works_msg = $("<td></td>");
// Load performance relationships
2014-12-29 06:15:31 +00:00
var CURRENT_PAGE = 1;
var TOTAL_PAGES = 1;
var page_numbers = $(".pagination .sel")[0];
2014-12-29 06:15:31 +00:00
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);
2014-12-29 06:15:31 +00:00
}
2014-12-29 06:15:31 +00:00
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) +
2014-12-29 06:15:31 +00:00
"&fmt=json"
);
}
2014-12-29 06:15:31 +00:00
function request_recordings(url) {
var attempts = 1;
2014-12-29 06:15:31 +00:00
$.get(url, function (data) {
var recs = data.recordings;
var cache = {};
function extract_rec(node) {
2014-12-29 06:15:31 +00:00
var row = cache[node.id];
2014-12-29 06:15:31 +00:00
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];
2014-12-29 05:25:47 +00:00
2014-12-29 06:15:31 +00:00
if (node.id === row_id) {
row = row_;
break;
} else {
cache[row_id] = row_;
}
}
}
2014-12-29 06:15:31 +00:00
if (row !== undefined) {
parse_recording(node, $(row));
recordings_not_parsed -= 1;
}
2014-12-29 06:15:31 +00:00
}
if (recs) {
_.each(recs, extract_rec);
} else {
extract_rec(data);
}
2014-12-29 06:15:31 +00:00
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);
});
}
2014-12-29 06:15:31 +00:00
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);
}
2014-12-29 06:15:31 +00:00
});
}
2014-12-29 06:15:31 +00:00
function parse_recording(node, $row) {
2015-01-04 20:37:55 +00:00
var $attrs = $row.children("td.bpr_attrs");
var performed = false;
2014-12-29 06:15:31 +00:00
$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;
}
2014-12-29 06:15:31 +00:00
if (rel.begin) {
$attrs.find("input.date").val(rel.begin).trigger("input");
}
var attrs = [];
_.each(rel.attributes, function (name) {
2015-01-04 20:37:55 +00:00
var cannonical_name = name.toLowerCase();
var $button = $attrs.find("span." + cannonical_name);
2014-12-29 05:25:47 +00:00
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);
}
2014-12-29 06:15:31 +00:00
});
//Use the dates in "live YYYY-MM-DD" disambiguation comments
2014-12-29 06:15:31 +00:00
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");
2014-12-29 06:15:31 +00:00
}
2014-12-29 06:15:31 +00:00
if (!performed) {
if (node.title.match(/.+\(live.*\)/) || (comment && comment.match && comment.match(/^live.*/))) {
2014-12-29 06:15:31 +00:00
$attrs.find("span.live").click();
} else {
var url = "/ws/2/recording/" + node.id + "?inc=releases+release-groups&fmt=json";
2014-12-29 06:15:31 +00:00
var request_rec = function () {
$.get(url, function (data) {
var releases = data.releases;
2014-12-29 06:15:31 +00:00
for (var i = 0; i < releases.length; i++) {
if (_.includes(releases[i]["release-group"]["secondary-types"], "Live")) {
2014-12-29 06:15:31 +00:00
$attrs.find("span.live").click();
break;
}
2014-12-29 06:15:31 +00:00
}
}).fail(function () {
ws_requests.push(request_rec);
});
}
2014-12-29 06:15:31 +00:00
ws_requests.push(request_rec);
}
}
2014-12-29 06:15:31 +00:00
}
// Load works
2014-12-29 05:43:37 +00:00
var WORKS_LOAD_CACHE = [];
var LOADED_WORKS = {};
var LOADED_ARTISTS = {};
function load_works_init() {
2014-12-29 06:02:18 +00:00
var artists_string = localStorage.getItem("bpr_artists " + ARTIST_MBID);
2014-12-29 05:25:47 +00:00
var artists = [];
if (artists_string) {
artists = artists_string.split("\n");
}
function callback() {
if (artists.length > 0) {
2014-12-29 06:02:18 +00:00
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();
2014-12-29 05:43:37 +00:00
if (LOADED_ARTISTS[mbid]) {
return deferred.promise();
2014-12-29 05:43:37 +00:00
}
LOADED_ARTISTS[mbid] = true;
2014-12-29 05:43:37 +00:00
var $table_row = $("<tr></tr>");
var $button_cell = $("<td></td>").css("display", "none");
var $msg = $artist_works_msg;
if (mbid !== ARTIST_MBID) {
$msg = $("<td></td>");
$button_cell.append(
style_buttons($("<button>Remove</button>"))
2014-12-29 03:53:17 +00:00
.click(function () {
$table_row.remove();
remove_artist_works(mbid);
}));
}
var $reload = style_buttons($("<button>Reload</button>"))
2014-12-29 03:53:17 +00:00
.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);
2014-12-29 03:53:17 +00:00
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 = [];
2014-12-29 03:53:17 +00:00
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";
2014-12-29 03:53:17 +00:00
ws_requests.unshift(function () {
request_works(works_url, 0, -1, callback);
});
}
return deferred.promise();
}
function load_works_finish(result) {
2014-12-29 03:53:17 +00:00
var tmp_mbids = [];
var tmp_titles = [];
2014-12-29 05:32:50 +00:00
var tmp_comments = [];
2014-12-29 03:53:17 +00:00
var tmp_norm_titles = [];
_.each(result, function (parts) {
2014-12-29 03:53:17 +00:00
var mbid = parts.slice(0, 36);
var rest = parts.slice(36).split("\u00a0");
2014-12-29 05:43:37 +00:00
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]));
});
2014-12-29 05:32:50 +00:00
return [tmp_mbids, tmp_titles, tmp_comments, tmp_norm_titles];
}
function request_works(url, offset, count, callback) {
2014-12-29 05:25:47 +00:00
$.get(url + "&offset=" + offset, function (data, textStatus, jqXHR) {
if (count < 0) {
count = data['work-count'];
2014-12-29 03:53:17 +00:00
}
2014-12-29 05:25:47 +00:00
var works = data.works;
2014-12-29 03:53:17 +00:00
var loaded = [];
2014-12-29 05:25:47 +00:00
_.each(works, function (work) {
var comment = work.disambiguation;
loaded.push(work.id + work.title + (comment ? "\u00a0" + comment : ""));
});
2014-12-29 03:53:17 +00:00
callback(loaded, count - offset - works.length);
if (works.length + offset < count) {
2014-12-29 03:53:17 +00:00
ws_requests.unshift(function () {
request_works(url, offset + 100, count, callback);
});
}
2014-12-29 03:53:17 +00:00
}).fail(function () {
ws_requests.unshift(function () {
request_works(url, offset, count, callback);
});
});
}
2014-12-29 05:32:50 +00:00
function match_works(mbids, titles, comments, norm_titles) {
2014-12-29 03:53:17 +00:00
if (!mbids.length) {
return;
2014-12-29 03:53:17 +00:00
}
2014-12-29 03:53:17 +00:00
var $not_performed = $recordings.filter(":not(.performed)");
if (!$not_performed.length) {
return;
2014-12-29 03:53:17 +00:00
}
2014-12-29 06:02:18 +00:00
function sim(r, w) {
r = r || '';
w = w || '';
return r == w ? 0 : leven(r, w) / ((r.length + w.length) / 2);
2014-12-29 06:02:18 +00:00
}
var matches = {};
2014-12-29 03:53:17 +00:00
var to_recording = function ($rec, rec_title) {
if (rec_title in matches) {
var match = matches[rec_title];
2014-12-29 06:02:18 +00:00
suggested_work_link($rec, match[0], match[1], match[2]);
return;
}
var $progress = $("<span></span>");
rowTitleCell($rec).append(
$('<div class="suggested-work"></div>').append(
$("<span>Looking for matching work…</span>"), "&#160;", $progress)
.css({"font-size": "0.9em", "padding": "0.3em", "padding-left": "1em", "color": "orange"}));
2014-12-29 06:02:18 +00:00
var current = 0;
var context = { minScore: 0.250001, match: null };
var total = mbids.length;
2014-12-29 03:53:17 +00:00
var done = function () {
2014-12-29 06:02:18 +00:00
var match = context.match;
if (match !== null) {
matches[rec_title] = match;
2014-12-29 06:02:18 +00:00
suggested_work_link($rec, match[0], match[1], match[2]);
} else {
$progress.parent().remove();
}
};
2014-12-29 03:53:17 +00:00
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());
}
2014-12-29 03:53:17 +00:00
2014-12-29 06:02:18 +00:00
if (score < context.minScore) {
context.match = [mbids[j], titles[j], comments[j]];
2014-12-29 03:53:17 +00:00
if (score === 0) {
clearInterval(iid);
done();
return;
}
2014-12-29 06:02:18 +00:00
context.minScore = score;
}
2014-12-29 03:53:17 +00:00
if (j === total - 1) {
clearInterval(iid);
done();
}
}, 0);
};
for (var i = 0; i < $not_performed.length; i++) {
2014-12-29 03:53:17 +00:00
var $rec = $not_performed.eq(i);
2014-12-29 04:18:07 +00:00
var mbid = $rec.find(TITLE_SELECTOR).attr("href").match(MBID_REGEX)[0];
2014-12-29 03:53:17 +00:00
to_recording($rec, RECORDING_TITLES[mbid]);
}
}
2014-12-29 06:02:18 +00:00
function suggested_work_link($rec, mbid, title, comment) {
var $title_cell = rowTitleCell($rec);
$title_cell.children("div.suggested-work").remove();
$title_cell.append(
$('<div class="suggested-work"></div>').append(
$("<span>Suggested work:</span>").css({"color": "green", "font-weight": "bold"}), "&#160;",
$("<a></a>")
.attr("href", "/work/" + mbid)
.text(title),
2014-12-29 05:32:50 +00:00
(comment ? "&#160;" : null),
(comment ? $("<span></span>").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) {
2014-12-29 05:43:37 +00:00
if (!LOADED_ARTISTS[mbid]) {
return;
2014-12-29 03:53:17 +00:00
}
2014-12-29 05:43:37 +00:00
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"));
}
2014-12-29 05:32:50 +00:00
function cache_work(mbid, title, comment) {
2014-12-29 05:43:37 +00:00
LOADED_WORKS[mbid] = true;
WORKS_LOAD_CACHE.push(mbid + title + (comment ? "\u00a0" + comment : ""));
2014-12-29 03:53:17 +00:00
var norm_title = normalizeTitle(title);
2014-12-29 06:02:18 +00:00
var works_date = localStorage.getItem("bpr_works_date " + ARTIST_MBID);
2014-12-29 03:53:17 +00:00
var count = $artist_works_msg.data("works_count") + 1;
2014-12-29 06:02:18 +00:00
update_artist_works_msg($artist_works_msg, count, ARTIST_NAME, works_date);
2014-12-29 05:32:50 +00:00
match_works([mbid], [title], [comment], [norm_title]);
}
function flush_work_cache() {
2014-12-29 05:43:37 +00:00
if (!WORKS_LOAD_CACHE.length) {
return;
2014-12-29 03:53:17 +00:00
}
2014-12-29 06:02:18 +00:00
var works_string = localStorage.getItem("bpr_works " + ARTIST_MBID);
if (works_string) {
2014-12-29 05:43:37 +00:00
works_string += "\n" + WORKS_LOAD_CACHE.join("\n");
} else {
2014-12-29 05:43:37 +00:00
works_string = WORKS_LOAD_CACHE.join("\n");
}
2014-12-29 06:02:18 +00:00
localStorage.setItem("bpr_works " + ARTIST_MBID, works_string);
2014-12-29 05:43:37 +00:00
WORKS_LOAD_CACHE = [];
}
function load_artist_works_btn() {
var $input = $("#bpr-load-artist");
2014-12-29 03:53:17 +00:00
if (!$input.data("selected")) {
return;
2014-12-29 03:53:17 +00:00
}
2014-12-29 03:53:17 +00:00
var mbid = $input.data("mbid");
var name = $input.data("name");
load_artist_works(mbid, name).done(function () {
2014-12-29 06:02:18 +00:00
var artists_string = localStorage.getItem("bpr_artists " + ARTIST_MBID);
if (artists_string) {
artists_string += "\n" + mbid + name;
} else {
artists_string = mbid + name;
}
2014-12-29 06:02:18 +00:00
localStorage.setItem("bpr_artists " + ARTIST_MBID, artists_string);
});
}
2014-12-29 06:02:18 +00:00
function update_artist_works_msg($msg, count, name, works_date) {
2014-12-29 03:53:17 +00:00
$msg
.html("")
.append(
2014-12-29 06:02:18 +00:00
count + " works loaded for " + name + "<br/>",
2014-12-29 03:53:17 +00:00
$('<span>(cached ' + works_date + ')</span>').css({"font-size": "0.8em"})
)
.data("works_count", count);
}
// Edit creation
function relate_all_to_work(mbid, title, comment) {
var deferred = $.Deferred();
2014-12-29 03:53:17 +00:00
var $rows = checked_recordings();
var total = $rows.length;
2014-12-29 03:53:17 +00:00
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() });
}
}
2014-12-29 05:43:37 +00:00
if (!LOADED_WORKS[mbid]) {
2014-12-29 05:32:50 +00:00
cache_work(mbid, title, comment);
flush_work_cache();
}
return deferred.promise();
}
function relate_to_new_titled_work() {
2014-12-29 03:53:17 +00:00
var $rows = checked_recordings();
var total = $rows.length;
var title = $("#bpr-new-work").val();
2014-12-29 03:53:17 +00:00
if (!total || !title) {
return;
2014-12-29 03:53:17 +00:00
}
ws_requests.stopped = true;
2014-12-29 03:53:17 +00:00
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() {
2014-12-29 03:53:17 +00:00
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() {
2014-12-29 03:53:17 +00:00
var $rows = checked_recordings();
var total_rows = $rows.length;
2014-12-29 03:53:17 +00:00
if (!total_rows) {
return;
2014-12-29 03:53:17 +00:00
}
ws_requests.stopped = true;
var $button = $(this)
.attr("disabled", true)
.css("color", "#EAEAEA");
2014-12-29 03:53:17 +00:00
$.each($rows, function (i, row) {
var $row = $(row);
var $title_cell = rowTitleCell($row);
2014-12-29 04:18:07 +00:00
var title = $title_cell.find(TITLE_SELECTOR).text();
2014-12-29 03:53:17 +00:00
$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() {
2014-12-29 03:53:17 +00:00
var data = "edit-work.name=" + title;
_.each($work_options, function($obj, kind) {
if ($obj.val()) {
data += "&edit-work." + kind + "_id=" + $obj.val();
}
});
2014-12-29 03:53:17 +00:00
$.post("/work/create", data, callback).fail(function () {
edit_requests.unshift(post_edit);
});
}
edit_requests.push(post_edit);
}
function relate_to_suggested_works() {
2014-12-29 03:53:17 +00:00
var $rows = checked_recordings().filter(function () {
return $(this).data("suggested_work_mbid");
});
2014-12-29 03:53:17 +00:00
var total = $rows.length;
if (!total) {
return;
2014-12-29 03:53:17 +00:00
}
2014-12-29 08:05:25 +00:00
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");
};
2014-12-29 03:53:17 +00:00
$.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");
2014-12-29 19:00:19 +00:00
var promise = relate_to_work($row, mbid, title, "", false, false);
2014-12-29 03:53:17 +00:00
if (i === total - 1) {
promise.done(callback);
}
});
}
2014-12-29 05:32:50 +00:00
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($('<div class="work"></div>')
2014-12-29 05:25:47 +00:00
.text(attrs.join(' ') + " recording of ")
.css({"font-size": "0.9em", "padding": "0.3em", "padding-left": "1em"})
.append($("<a></a>").attr("href", "/work/" + mbid).text(title),
2014-12-29 05:32:50 +00:00
(comment ? "&#160;" : null),
(comment ? $("<span></span>").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) {
2014-12-29 03:53:17 +00:00
if (performances.indexOf(work_mbid) === -1) {
performances.push(work_mbid);
} else {
deferred.resolve();
return deferred.promise();
}
} else {
$row.data("performances", [work_mbid]);
}
2014-12-29 04:18:07 +00:00
var rec_mbid = $row.find(TITLE_SELECTOR).attr("href").match(MBID_REGEX)[0];
2014-12-29 03:53:17 +00:00
var $title_cell = rowTitleCell($row);
var title_link = $title_cell.children("a")[0];
var $attrs = $row.children("td.bpr_attrs");
2014-12-29 05:25:47 +00:00
var selectedAttrs = [];
function selected(attr) {
var checked = $attrs.children("span." + attr).data("checked") ? 1 : 0;
if (checked) {
2014-12-29 05:25:47 +00:00
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");
2014-12-29 03:53:17 +00:00
_.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");
2014-12-29 05:25:47 +00:00
$.post('/relationship-editor', data, function () {
2014-12-29 05:32:50 +00:00
add_work_link($row, work_mbid, work_title, work_comment, selectedAttrs);
$(title_link).removeAttr("style");
$row.addClass("performed");
if (hide_performed_recs) {
2014-12-29 08:05:25 +00:00
uncheckRows($row.hide());
2014-12-29 03:31:58 +00:00
restripeRows();
}
deferred.resolve();
2014-12-29 03:53:17 +00:00
}).fail(function () {
edit_requests.unshift(post_edit);
});
}
if (priority) {
edit_requests.unshift(post_edit);
} else {
edit_requests.push(post_edit);
}
if (check_loaded) {
2014-12-29 05:43:37 +00:00
if (!LOADED_WORKS[work_mbid]) {
2014-12-29 05:32:50 +00:00
cache_work(work_mbid, work_title, work_comment);
2014-12-29 03:53:17 +00:00
}
}
return deferred.promise();
}
function filter_recordings() {
var string = this.value.toLowerCase();
for (var i = 0; i < $recordings.length; i++) {
2014-12-29 03:53:17 +00:00
var $rec = $recordings.eq(i);
2014-12-29 04:18:07 +00:00
var title = $rec.find(TITLE_SELECTOR).text().toLowerCase();
2014-12-29 03:53:17 +00:00
if (title.indexOf(string) !== -1) {
$rec.data("filtered", false);
2014-12-29 03:53:17 +00:00
if (!hide_performed_recs || !$rec.hasClass("performed")) {
$rec.show();
2014-12-29 03:53:17 +00:00
}
} else {
$rec.hide().data("filtered", true);
}
}
2014-12-29 03:31:58 +00:00
restripeRows();
}
function toggle_performed_recordings() {
var $performed = $recordings.filter(".performed");
hide_performed_recs = this.checked;
if (hide_performed_recs) {
2014-12-29 08:05:25 +00:00
uncheckRows($performed.hide());
} else {
2014-12-29 03:53:17 +00:00
$performed.filter(function () { return !$(this).data("filtered") }).show();
}
2014-12-29 03:31:58 +00:00
restripeRows();
2015-08-21 22:56:42 +00:00
setting('hide_performed_recs', hide_performed_recs.toString());
}
function toggle_pending_edits(event, checked) {
2014-12-29 03:53:17 +00:00
var $pending = $recordings.filter(function () {
2014-12-29 04:18:07 +00:00
return $(this).find(TITLE_SELECTOR).parent().parent().is("span.mp");
});
hide_pending_edits = checked !== undefined ? checked : this.checked;
if (hide_pending_edits) {
2014-12-29 08:05:25 +00:00
uncheckRows($pending.hide());
} else {
2014-12-29 03:53:17 +00:00
$pending.filter(function () { return !$(this).data("filtered") }).show();
}
2014-12-29 03:31:58 +00:00
restripeRows();
2015-08-21 22:56:42 +00:00
setting('hide_pending_edits', hide_pending_edits.toString());
}
toggle_pending_edits(null, hide_pending_edits);
function checked_recordings() {
return $recordings
.filter(":visible")
2014-12-29 03:53:17 +00:00
.filter(function () { return $(this).find("input[name=add-to-merge]:checked").length });
}
2015-01-02 01:37:08 +00:00
function entity_lookup(id_suffix, entity) {
var $input = $('<input type="text" id="bpr-' + id_suffix + '"/>')
2014-12-29 08:05:25 +00:00
$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};
2014-12-29 03:53:17 +00:00
if (entity === "work" && data.disambiguation) {
out_data.comment = data.disambiguation;
}
2014-12-29 03:53:17 +00:00
$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;
}
2014-12-29 03:31:58 +00:00
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) {
2014-12-29 04:18:07 +00:00
return $row.children('td:has(' + TITLE_SELECTOR + ')');
}
2016-05-23 21:35:20 +00:00
}