(function () {
let winwidth; // ширина текущего окна браузера
let winheight; // высота текущего окна браузера
const imagewidth = 1320; // ширина файла изображения
const imageheight = 1900; // высота файла изображения
const minimagewidth = 900; // минимальная ширина показанного изображения
const minimageheight = imageheight * minimagewidth / imagewidth; // минимальная высота показанного изображения
const minlistwidth = 200; // минимальная ширина подокна списка ссылок
let isvertical; // флаг, показывающий, находится ли список рядом с изображением, или под ним
let multiplier = 1; // мультипликатор увеличения изображения
let originalmap; // бэкап копия тега imagemap
/*
Пересоздание структуры в зависимости от новых данных
leftwidth текущая ширина подокна изображения
rightwidth текущая ширина подокна списка ссылок
upheight текущая высота подокна изображения
downheight текущая высота подокна списка ссылок
*/
function resize(leftwidth = $('#mapwrapper').width(), rightwidth = $('#listwrapper').width(),
upheight = $('#mapwrapper').height(), downheight = $('#listwrapper').height()) {
const mapwidth = Math.floor((Math.max(minimagewidth, leftwidth) - 10) * multiplier); // текущая ширина видимого изображения
const mapheight = Math.floor(mapwidth * imageheight / imagewidth); // текущая высота видимого изображения
const colored = $('.liHighlighting a'); // текущая выделенная ссылка в списке ссылок
$('#mycontent #mapwrapper').width(leftwidth); // установить ширину элемента, содержащего mapplace
$('#mycontent #mapwrapper').height(upheight); // установить высоту элемента, содержащего mapplace
$('#mycontent #mapplace').width(leftwidth); // установить ширину элемента, содержащего изображение
$('#mycontent #mapplace').height(upheight); // установить высоту элемента, содержащего изображение
$('#mycontent #map').width(mapwidth); // установить ширину изображения
$('#mycontent #map').height(mapheight); // установить высоту изображения
$('#mycontent #map div').width(mapwidth); // установить ширину элемента, содержащего подсветку изображения
$('#mycontent #map div').height(mapheight); // установить высоту элемента, содержащего подсветку изображения
$($('#mycontent #map div div')[0]).width(mapwidth); // установить ширину элемента, частично содержащего подсветку изображения
$($('#mycontent #map div div')[0]).height(mapheight); // установить высоту элемента, частично содержащего подсветку изображения
$($('#mycontent #map div div')[0]).children().width(mapwidth); // установить ширину элементов внутри элемента, частично содержащего подсветку изображения
$($('#mycontent #map div div')[0]).children().height(mapheight); // установить высоту элементов внутри элемента, частично содержащего подсветку изображения
$($('#mycontent #map div div')[0]).children().attr({width: mapwidth, height: mapheight}); // установить атрибуты размеров элементов внутри элемента, частично содержащего подсветку изображения
$('#mycontent #listwrapper').width(rightwidth); // установить ширину элемента, содержащего listplace
$('#mycontent #listwrapper').height(downheight); // установить высоту элемента, содержащего listplace
$('#mycontent #listplace').width(rightwidth - 20); // установить ширину элемента, содержащего список ссылок
$('#mycontent #listplace').height(downheight - 20); // установить высоту элемента, содержащего список ссылок
$('#mycontent #list').width(rightwidth - 20); // установить ширину списка ссылок
$('#mycontent #list').height(downheight - 20); // установить высоту списка ссылок
$('#mycontent #list div').width(rightwidth - 20); // установить ширину элемента, обхватывающего список ссылок
$('#mycontent #list div').height(downheight - 20); // установить высоту элемента, обхватывающего список ссылок
recalculatemap(colored); // вычислить новые координаты подсветок
if (Math.abs($('#mycontent').width() - $('#mapwrapper').width()) < 20) { // в случае, если надо вертикально
isvertical = true; // установить, что будет вертикально
$(".ui-resizable-s").on({ // настроить мышь и тачскрин
mouseenter: function () { // при наведении мыши
$(this).css('background-image', 'linear-gradient(green , yellow)'); // показать жёлто-зелёную горизонтальную разделительную черту
},
mouseleave: function () { // при убирании мыши
$(this).css('background-image', 'initial'); // убрать горизонтальную разделительную черту
},
touchstart: function () { // при нажатии на тачскрин
$(this).trigger('mouseenter'); // запустить событие наведения мыши
},
touchmove: OO.ui.debounce(function (event) { // при движении пальца, нажатого на тачскрин
let y = Math.min(Math.max(Math.floor(event.touches[0].clientY), 25), winheight - 25); // вычислить разницу вертикального сдвига
resize(undefined, undefined, y, winheight - y); // переформатировать структуру соответственно
}, 100), // сколько ждать
touchend: function () { // при отпускании тачскрина
$(this).trigger('mouseleave'); // запустить событие убирания мыши
}
});
$(".ui-resizable-e").on('mouseenter mouseleave', function () { // для вертикальной разделительной черты
$(this).css('background-image', 'initial'); // спрятать, как ненужную
});
$('.ui-resizable-s').css('cursor', 's-resize'); // установить курсор для горизонтальной разделительной черты
$('.ui-resizable-e').css('cursor', 'default'); // восстановить курсор для вертикальной разделительной черты
$('#mapwrapper').resizable('option', { // для элемента, содержащего изображение, установить
maxHeight: winheight - 25, // максимальную высоту
minHeight: 25, // минимальную высоту
maxWidth: leftwidth, // максимальную ширину
minWidth: leftwidth, // минимальную ширину
handles: 's', // определить горизонтальную разделительную черту
resize: function(e, ui) { // функция смены размера для мыши
resize(undefined, undefined, undefined, winheight - $('#mapwrapper').height()); // переформатировать структуру соответственно
}
});
} else { // в случае, если надо горизонтально
isvertical = false; // установить, что будет горизонтально
$(".ui-resizable-e").on({ // настроить мышь и тачскрин
mouseenter: function () { // при наведении мыши
$(this).css('background-image', 'linear-gradient(to right, green , yellow)'); // показать жёлто-зелёную вертикальную разделительную черту
},
mouseleave: function () { // при убирании мыши
$(this).css('background-image', 'initial'); // убрать вертикальную разделительную черту
},
touchstart: function () { // при нажатии на тачскрин
$(this).trigger('mouseenter'); // запустить событие наведения мыши
},
touchmove: OO.ui.debounce(function (event) { // при движении пальца, нажатого на тачскрин
let x = Math.min(Math.max(Math.floor(event.touches[0].clientX), 25), winwidth - 25); // вычислить разницу горизонтального сдвига
resize(x, winwidth - x); // переформатировать структуру соответственно
}, 100), // сколько ждать
touchend: function () { // при отпускании тачскрина
$(this).trigger('mouseleave'); // запустить событие убирания мыши
}
});
$(".ui-resizable-s").on('mouseenter mouseleave', function () { // для горизонтальной разделительной черты
$(this).css('background-image', 'initial'); // спрятать, как ненужную
});
$('.ui-resizable-s').css('cursor', 'default'); // восстановить курсор для горизонтальной разделительной черты
$('.ui-resizable-e').css('cursor', 'e-resize'); // установить курсор для вертикальной разделительной черты
$('#mapwrapper').resizable('option', { // для элемента, содержащего изображение, установить
maxHeight: upheight, // максимальную высоту
minHeight: upheight, // минимальную высоту
maxWidth: winwidth - 25, // максимальную ширину
minWidth: 25, // минимальную ширину
handles: 'e', // определить вертикальную разделительную черту
resize: function(e, ui) { // функция смены размера для мыши
resize(undefined, winwidth - $('#mapwrapper').width()); // переформатировать структуру соответственно
}
});
}
}
/*
Одна итерация работы при возможной смене размеров окна браузера
*/
function test() {
let svgwidth; // ширина изображения
let newwinwidth = Math.floor($('.mw-parser-output').width()); // новая ширина текущего окна браузера
let newwinheight = $(window).height() - 30; // новая высота текущего окна браузера
if (newwinwidth == winwidth && newwinheight == winheight) // если нет изменений
return; // ничего не делать
winwidth = newwinwidth; // установить ширину текущего окна браузера
winheight = newwinheight; // установить высоту текущего окна браузера
if (winwidth <= minimagewidth + minlistwidth) { // 1. если изображение и список ссылок не помещаются в ширину
svgwidth = Math.max(minimagewidth, winwidth); // вычислить ширину изображения
const svgheight = Math.floor(svgwidth * imageheight / imagewidth); // вычислить высоту изображения
const upheight = Math.min(svgheight, Math.floor(0.8 * winheight)); // вычислить высоту подокна изображения
resize(winwidth, winwidth, upheight, winheight - upheight); // переформатировать структуру соответственно
} else if (winheight <= minimageheight) // 2. если нет, и изображение не помещается в высоту
resize(minimagewidth, winwidth - minimagewidth, winheight, winheight); // переформатировать структуру соответственно
else if ((winwidth - minlistwidth) / winheight > imagewidth / imageheight) { // 3. если нет, и ширина места для изображения больше, чем потребовалось, если изменить размер
// изображения под высоту окна браузера
svgwidth = Math.floor(winheight * imagewidth / imageheight); // вычислить ширину изображения
resize(svgwidth, winwidth - svgwidth, winheight, winheight); // переформатировать структуру соответственно
} else { // 4. если нет
svgwidth = winwidth - minlistwidth; // вычислить ширину изображения
resize(svgwidth, minlistwidth, winheight, svgwidth); // переформатировать структуру соответственно
}
}
/*
Вычислить новые координаты подсветок
colored текущая выделенная ссылка
*/
function recalculatemap(colored) {
let curmap = $('.imgtogglefull map');
let f = function (item, index, arr) {
arr[index] = Math.round(item * $('#map div div').width() / (minimagewidth - 10));
};
for (let i = 0; i < curmap.find('area').length; i++) {
let tmparr = originalmap.find('area')[i].coords.split(',');
tmparr.forEach(f);
$(curmap.find('area')[i]).attr('coords', tmparr.join(','));
}
$('body').trigger('refresh-imagehighlight-links1');
colored.trigger('mouseenter');
}
function build() {
new mw.Api().get({
action: 'parse',
contentmodel: 'wikitext',
format: 'json',
formatversion: 2,
prop: 'text',
text: '{{#invoke:MetroMap/песочница|tempimagemap|Московского|Empty svg for Moscow metro map.svg|pagename=a}}'
}).done(function(data) {
let f1 = $('<div>', {
id: 'mapwrapper'
});
let f2 = $('<div>', {
id: 'mapplace'
}).css('overflow', 'auto');
let f3 = $(data.parse.text).attr({
id: 'map'
});
let l1 = $('<div>', {
id: 'listwrapper'
});
let l2 = $('<div>', {
id: 'listplace'
}).css({
overflowY: 'auto',
margin: '10px'
});
let l3 = $('<div>', {
id: 'list'
});
f3.find('img').attr({
srcset: '',
src: '//upload.wikimedia.org/wikipedia/ru/f/f8/Moscow_metro_map_sb_local_draft.svg'
});
$('#mycontent').css({
display: 'flex',
flexWrap: 'wrap'
}).addClass('popup-table popup-version-opened-Московского ts-ИСММ ts-ОКИ')
.append(f1.append(f2.append(f3)))
.append(l1.append(l2.append(l3)));
$('#mapwrapper').resizable({});
$('#mapwrapper').on('wheel mousewheel DOMMouseScroll', function (event) {
let parentOffset = $("#mapplace").offset();
let x = event.pageX ? event.pageX - parentOffset.left : 0;
let y = event.pageY ? event.pageY - parentOffset.top : 0;
let newmultiplier = event.deltaY > 0 || event.originalEvent.wheelDelta > 0 || event.originalEvent.detail < 0
? Math.min(multiplier * 1.2, 8) : Math.max(multiplier * 0.8, 1);
let curmultiplier = newmultiplier / multiplier;
if ((x > 30 && x < $('#mapplace').width() - 30 && y > 30 && y < $('#mapplace').height() - 30) || ! event.pageX) {
multiplier = newmultiplier;
resize();
$('#mapplace')[0].scrollTo(Math.floor($("#mapplace").offset().left - $("#map").offset().left * curmultiplier + (event.pageX
|| parentOffset.left) * (curmultiplier - 1)), Math.floor($("#mapplace").offset().top - $("#map").offset().top
* curmultiplier + (event.pageY || parentOffset.top) * (curmultiplier - 1)), {behavior: 'instant'});
event.preventDefault();
}
});
originalmap = $('.imgtogglefull map').clone();
$('#mapwrapper').on('mouseup', function (event) {
if (event.which == 2)
multiplier = 1;
resize();
event.preventDefault();
});
$('#mapwrapper').on('mousemove', function (e) {
let parentOffset = $(this).parent().offset();
let x = e.pageX - parentOffset.left;
let y = e.pageY - parentOffset.top;
if (x < 30)
$('#mapplace')[0].scrollBy(-10, 0, {behavior: "smooth"});
else if (x > $('#mapwrapper').width() - 30)
$('#mapplace')[0].scrollBy(10, 0, {behavior: "smooth"});
else if (y < 30)
$('#mapplace')[0].scrollBy(0, -10, {behavior: "smooth"});
else if (y > $('#mapwrapper').height() - 30)
$('#mapplace')[0].scrollBy(0, 10, {behavior: "smooth"});
event.preventDefault();
});
$('body').trigger('refresh-imagehighlight-links1');
let scaling, dist, newdist;
$('#mapwrapper').on({
touchstart: function (event) {
if (event.touches.length === 2) {
scaling = true;
dist = undefined;
if (event.cancelable)
event.preventDefault();
}
},
touchmove: function (event) {
if (scaling) {
newdist = Math.hypot(event.touches[0].pageX - event.touches[1].pageX, event.touches[0].pageY - event.touches[1].pageY);
if (dist) {
let parentOffset = $("#mapplace").offset();
let x = Math.floor((event.touches[0].pageX + event.touches[1].pageX) / 2) - parentOffset.left;
let y = Math.floor((event.touches[0].pageY + event.touches[1].pageY) / 2) - parentOffset.top;
let newmultiplier = newdist > dist ? Math.min(multiplier * 1.2, 8) : Math.max(multiplier * 0.8, 1);
let curmultiplier = newmultiplier / multiplier;
multiplier = newmultiplier;
resize();
$('#mapplace')[0].scrollTo(Math.floor($("#mapplace").offset().left - $("#map").offset().left * curmultiplier + x
* (curmultiplier - 1)), Math.floor($("#mapplace").offset().top - $("#map").offset().top * curmultiplier + y
* (curmultiplier - 1)), {behavior: 'instant'});
}
dist = newdist;
if (event.cancelable)
event.preventDefault();
}
},
touchend: function (event) {
if (scaling) {
scaling = false;
if (event.cancelable)
event.preventDefault();
}
}
});
$('#list').html($('.mw-collapsible'));
test();
keys();
});
}
function keys() {
let cur, next;
$(document).on('keydown', function (event) {
switch (event.which) {
case 65: // a
if (! isvertical) {
cur = $('#mycontent #mapwrapper').width();
next = Math.max(25, cur - 50);
resize(next, winwidth - next);
event.preventDefault();
}
break;
case 68: // d
if (! isvertical) {
cur = $('#mycontent #mapwrapper').width();
next = Math.min(winwidth - 25, cur + 50);
resize(next, winwidth - next);
event.preventDefault();
}
break;
/*case 69: // e tmp
mw.notify(`${$('#mapplace').scrollLeft()},${$('#mapplace')[0].offsetWidth},${$('#mapplace')[0].clientWidth},${$('#mapplace').width()},`);
break;*/
case 87: // s
if (isvertical) {
cur = $('#mycontent #mapwrapper').height();
next = Math.max(25, cur - 50);
resize(undefined, undefined, next, winheight - next);
event.preventDefault();
}
break;
case 83: // w
if (isvertical) {
cur = $('#mycontent #mapwrapper').height();
next = Math.min(winheight - 25, cur + 50);
resize(undefined, undefined, next, winheight - next);
event.preventDefault();
}
break;
case 61:
case 187: // +
multiplier = Math.min(multiplier * 1.2, 8);
resize();
event.preventDefault();
break;
case 48: // 0
multiplier = 1;
resize();
event.preventDefault();
break;
case 173:
case 189: // -
multiplier = Math.max(multiplier * 0.8, 1);
resize();
event.preventDefault();
break;
case 37: // <-
if (event.shiftKey)
$('#mapplace')[0].scrollBy(-10, 0, {behavior: "smooth"});
break;
case 38: // |^
if (event.shiftKey)
$('#mapplace')[0].scrollBy(0, -10, {behavior: "smooth"});
break;
case 39: // ->
if (event.shiftKey)
$('#mapplace')[0].scrollBy(10, 0, {behavior: "smooth"});
break;
case 40: { // |v
if (event.shiftKey)
$('#mapplace')[0].scrollBy(0, 10, {behavior: "smooth"});
break;
}
default:
}
});
}
mw.loader.using( ['oojs-ui-core', 'jquery.ui', 'mediawiki.api'], function() {
if (mw.config.get('wgPageName') === 'Участник:IKhitron/testpopup' && $('.mw-parser-output').length) {
$('.vector-sticky-header-container').css({ display: 'none' });
mw.util.addCSS('.ui-icon {visibility:hidden;}');
mw.util.addCSS('#mapwrapper {touch-action: none}');
$(build);
$(window).on('resize orientationchange', OO.ui.debounce(test, 1000));
}
});
})();