Перейти к содержанию
Главное меню
Главное меню
переместить в боковую панель
скрыть
Навигация
Основной сайт
Онлайн-карта
Свежие правки
Случайная страница
Справка по MediaWiki
Вики «Русская MTR»
Поиск
Найти
Внешний вид
Создать учётную запись
Войти
Персональные инструменты
Создать учётную запись
Войти
Страницы для неавторизованных редакторов
узнать больше
Вклад
Обсуждение
Редактирование:
Модуль:ТранспортнаяСхема
Модуль
Обсуждение
English
Читать
Править код
История
Инструменты
Инструменты
переместить в боковую панель
скрыть
Действия
Читать
Править код
История
Очистить
Общие
Ссылки сюда
Связанные правки
Сведения о странице
Внешний вид
переместить в боковую панель
скрыть
Внимание:
Вы не вошли в систему. Ваш IP-адрес будет общедоступен, если вы запишете какие-либо изменения. Если вы
войдёте
или
создадите учётную запись
, её имя будет использоваться вместо IP-адреса, наряду с другими преимуществами.
Анти-спам проверка.
Не
заполняйте это!
local p = {} -- Извлечение аргументов local function getArgs(frame) local args = {} if frame.getParent then local parent = frame:getParent() if parent and parent.args then for k, v in pairs(parent.args) do args[k] = v end end end if frame.args then for k, v in pairs(frame.args) do args[k] = v end end return args end -- Безопасная очистка текста JSON local function cleanJsonText(rawText) rawText = mw.ustring.gsub(rawText, '<noinclude>.-</noinclude>', '') rawText = string.gsub(rawText, '<!%-%-.-%-%->', '') rawText = string.gsub(rawText, '\194\160', ' ') rawText = string.gsub(rawText, '\194\173', '') return mw.text.trim(rawText) end -- Загрузка данных из data.json local function loadData(args) local dataParam = args['данные'] or args[1] or 'Муроморского' dataParam = mw.text.trim(dataParam) local cleanName = mw.ustring.gsub(dataParam, '^[Шш][Аа][Бб][Лл][Оо][Нн]:', '') cleanName = mw.ustring.gsub(cleanName, '^[Tt][Ee][Mm][Pp][Ll][Aa][Tt][Ee]:', '') cleanName = mw.ustring.gsub(cleanName, '/data%.json$', '') cleanName = mw.ustring.gsub(cleanName, '%.json$', '') cleanName = mw.ustring.gsub(cleanName, '^[Ии]нтерактивная схема%s*', '') cleanName = mw.text.trim(cleanName) local candidates = {} local function addCandidate(title) if title and title ~= '' then for _, existing in ipairs(candidates) do if existing == title then return end end table.insert(candidates, title) end end addCandidate(dataParam) addCandidate('Шаблон:Интерактивная схема ' .. cleanName .. '/data.json') addCandidate('Шаблон:Интерактивная схема ' .. cleanName .. ' метрополитена/data.json') addCandidate('Шаблон:' .. cleanName .. '/data.json') addCandidate('Шаблон:' .. cleanName) addCandidate('Интерактивная схема ' .. cleanName .. '/data.json') addCandidate('Интерактивная схема ' .. cleanName .. ' метрополитена/data.json') addCandidate(cleanName .. '/data.json') addCandidate(cleanName) local jsonErrors = {} for _, pageTitle in ipairs(candidates) do local ok, data = pcall(mw.loadJsonData, pageTitle) if ok and type(data) == 'table' then return data end local titleObj = mw.title.new(pageTitle) if titleObj then local rawText = titleObj:getContent() if rawText and rawText ~= '' then rawText = cleanJsonText(rawText) local decOk, decoded = pcall(mw.text.jsonDecode, rawText) if decOk and type(decoded) == 'table' then return decoded else table.insert(jsonErrors, '[[' .. pageTitle .. ']] (' .. tostring(decoded) .. ')') end end end end if #jsonErrors > 0 then error('Ошибка в синтаксисе JSON:\n* ' .. table.concat(jsonErrors, '\n* ')) end error('Страница данных не найдена ни по одному из путей:\n* [[' .. table.concat(candidates, ']]\n* [[') .. ']]') end -- Парсер координат "X Y" local function parseCoords(str) if not str then return nil, nil end local x, y = mw.ustring.match(str, '(%d+%.?%d*)%s+(%d+%.?%d*)') return tonumber(x), tonumber(y) end -- Генерация интерактивной Canvas-схемы с панорамированием и управлением function p.canvas(frame) local args = getArgs(frame) local data = loadData(args) local origW = tonumber(args['ширина_оригинала'] or 850) or 850 local origH = tonumber(args['высота_оригинала'] or 620) or 620 -- Кадрирование local cropX, cropY, cropW, cropH = nil, nil, nil, nil if args['кадрирование'] or args['crop'] then local c1, c2, c3, c4 = mw.ustring.match(args['кадрирование'] or args['crop'], '(%d+%.?%d*)%s+(%d+%.?%d*)%s+(%d+%.?%d*)%s+(%d+%.?%d*)') cropX, cropY, cropW, cropH = tonumber(c1), tonumber(c2), tonumber(c3), tonumber(c4) elseif args['фокус'] or args['focus'] then local fx, fy = parseCoords(args['фокус'] or args['focus']) local fw = tonumber(args['ширина_фокуса'] or 320) or 320 local fh = tonumber(args['высота_фокуса'] or 240) or 240 if fx and fy then cropX = math.max(0, fx - fw / 2) cropY = math.max(0, fy - fh / 2) cropW = fw cropH = fh end end local targetW = cropW or origW local targetH = cropH or origH local boxW = tonumber(args['ширина_блока'] or args['ширина_карточки'] or args['ширина']) local scale = tonumber(args['масштаб'] or args['scale']) local isScrollMode = (args['прокрутка'] == 'да' or args['скролл'] == 'да' or args['scroll'] == 'yes') if not isScrollMode and boxW and not scale then scale = boxW / targetW elseif not scale then scale = 1.0 end local finalBoxW = boxW or (targetW * scale) local finalBoxH = tonumber(args['высота_блока'] or args['высота_карточки'] or args['высота']) or (targetH * scale) -- Внешний контейнер-вьюпорт с поддержкой перемещения клавиатурой и скролла local wrapper = mw.html.create('div') :addClass('ts-transport-map-viewport') :attr('tabindex', '0') :attr('title', 'Кликните для управления стрелками клавиатуры (← ↑ → ↓) или скролла мышью') :css({ ['position'] = 'relative', ['width'] = string.format('%.1fpx', finalBoxW), ['height'] = string.format('%.1fpx', finalBoxH), ['max-width'] = '100%', ['overflow'] = isScrollMode and 'auto' or 'hidden', ['scroll-behavior'] = 'smooth', ['overscroll-behavior'] = 'contain', ['-webkit-overflow-scrolling'] = 'touch', ['border-radius'] = '10px', ['border'] = args['без_рамки'] and 'none' or '1px solid #d0d7de', ['margin'] = (args['прижатие'] == 'лево' and '0 auto 0 0') or (args['прижатие'] == 'право' and '0 0 0 auto') or '0 auto', ['background-color'] = '#ffffff', ['background-image'] = 'radial-gradient(#e1e4e8 1px, transparent 1px)', ['background-size'] = '20px 20px', ['box-shadow'] = args['без_тени'] and 'none' or '0 2px 8px rgba(0,0,0,0.06)', ['outline'] = 'none', ['user-select'] = 'none' }) local shiftX = cropX or 0 local shiftY = cropY or 0 -- Внутренний холст схемы local canvas = wrapper:tag('div') :addClass('ts-html-transport-canvas') :css({ ['position'] = isScrollMode and 'relative' or 'absolute', ['left'] = not isScrollMode and string.format('%.1fpx', -shiftX * scale) or '0', ['top'] = not isScrollMode and string.format('%.1fpx', -shiftY * scale) or '0', ['width'] = origW .. 'px', ['height'] = origH .. 'px', ['transform'] = (scale ~= 1.0 and not isScrollMode) and string.format('scale(%.4f)', scale) or 'none', ['transform-origin'] = '0 0', ['font-family'] = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" }) -- Шапка/логотип if not (args['шапка'] == 'нет' or cropX ~= nil) then local header = canvas:tag('div') :css({ ['position'] = 'absolute', ['top'] = '16px', ['left'] = '18px', ['display'] = 'flex', ['align-items'] = 'center', ['gap'] = '10px', ['z-index'] = '5', ['background'] = 'rgba(255,255,255,0.92)', ['padding'] = '6px 12px', ['border-radius'] = '8px', ['border'] = '1px solid #e1e4e8', ['box-shadow'] = '0 2px 6px rgba(0,0,0,0.04)' }) header:tag('div') :css({ ['width'] = '24px', ['height'] = '24px', ['background'] = '#ED1B24', ['color'] = '#fff', ['font-weight'] = 'bold', ['font-size'] = '13px', ['display'] = 'flex', ['align-items'] = 'center', ['justify-content'] = 'center', ['border-radius'] = '50%' }):wikitext('M') header:tag('div') :css({['text-align'] = 'left'}) :tag('div'):css({['font-weight'] = 'bold', ['font-size'] = '13px', ['color'] = '#1f2328'}):wikitext(data.title or 'Скоростной транспорт'):done() :tag('div'):css({['font-size'] = '10px', ['color'] = '#57606a', ['text-transform'] = 'uppercase', ['letter-spacing'] = '0.5px'}):wikitext('Интерактивная схема линий'):done() end -- Индикатор подсказки управления if isScrollMode then wrapper:tag('div') :css({ ['position'] = 'sticky', ['top'] = '10px', ['left'] = 'calc(100% - 130px)', ['width'] = '115px', ['z-index'] = '10', ['background'] = 'rgba(255,255,255,0.88)', ['border'] = '1px solid #d0d7de', ['border-radius'] = '16px', ['padding'] = '3px 8px', ['font-size'] = '10px', ['color'] = '#57606a', ['display'] = 'flex', ['align-items'] = 'center', ['gap'] = '4px', ['pointer-events'] = 'none', ['box-shadow'] = '0 1px 4px rgba(0,0,0,0.05)', ['backdrop-filter'] = 'blur(2px)' }) :wikitext('⌨ ⇄ ⇅ Скролл') end if data.lines then -- 1. Слой путей for _, line in ipairs(data.lines) do local color = line.color or '#888888' local stations = line.stations or {} for i = 1, #stations - 1 do local st1, st2 = stations[i], stations[i+1] local x1, y1 = parseCoords(st1.coords) local x2, y2 = parseCoords(st2.coords) if x1 and y1 and x2 and y2 then local dx = x2 - x1 local dy = y2 - y1 local length = math.sqrt(dx * dx + dy * dy) local angle = math.deg(math.atan2(dy, dx)) local isFuture = (st1.future and st2.future) canvas:tag('div') :css({ ['position'] = 'absolute', ['left'] = x1 .. 'px', ['top'] = (y1 - 3) .. 'px', ['width'] = length .. 'px', ['height'] = isFuture and '0px' or '6px', ['background-color'] = isFuture and 'transparent' or color, ['border-top'] = isFuture and ('5px dashed ' .. color) or 'none', ['transform-origin'] = '0 50%', ['transform'] = 'rotate(' .. angle .. 'deg)', ['border-radius'] = '3px', ['z-index'] = '1', ['pointer-events'] = 'none' }) end end end -- 2. Слой станций и подписей for _, line in ipairs(data.lines) do local color = line.color or '#888888' for _, st in ipairs(line.stations or {}) do local x, y = parseCoords(st.coords) if x and y then local name = st.name or st.article or '' local article = st.article or name local isFuture = (st.future == true) local node = canvas:tag('div') :css({ ['position'] = 'absolute', ['left'] = (x - 7) .. 'px', ['top'] = (y - 7) .. 'px', ['width'] = '14px', ['height'] = '14px', ['border-radius'] = '50%', ['background-color'] = '#ffffff', ['border'] = isFuture and ('2.5px dashed ' .. color) or ('3px solid ' .. color), ['box-sizing'] = 'border-box', ['box-shadow'] = '0 1px 3px rgba(0,0,0,0.25)', ['z-index'] = '3', ['cursor'] = 'pointer' }) node:wikitext('[[' .. article .. '|<span style="display:block;width:100%;height:100%;" title="' .. name .. '"></span>]]') local isRightSide = (x >= (origW / 2)) local label = canvas:tag('div') :css({ ['position'] = 'absolute', ['left'] = isRightSide and ((x + 11) .. 'px') or ((x - 11) .. 'px'), ['top'] = y .. 'px', ['transform'] = isRightSide and 'translateY(-50%)' or 'translate(-100%, -50%)', ['text-align'] = isRightSide and 'left' or 'right', ['white-space'] = 'nowrap', ['font-size'] = '11.5px', ['font-weight'] = '500', ['z-index'] = '4', ['text-shadow'] = '0 0 3px #ffffff, 0 0 3px #ffffff, 0 0 3px #ffffff, 0 0 4px #ffffff' }) local labelStyle = isFuture and 'color: #6e7781; font-style: italic; text-decoration: none;' or 'color: #1f2328; text-decoration: none;' label:wikitext('[[' .. article .. '|<span style="' .. labelStyle .. '">' .. name .. '</span>]]') end end end end return tostring(wrapper) end return p
Описание изменений:
Пожалуйста, учтите, что любой ваш вклад в проект «MURS вики» может быть отредактирован или удалён другими участниками. Если вы не хотите, чтобы кто-либо изменял ваши тексты, не помещайте их сюда.
Вы также подтверждаете, что являетесь автором вносимых дополнений, или скопировали их из источника, допускающего свободное распространение и изменение своего содержимого (см.
MURS:Авторские права
).
НЕ РАЗМЕЩАЙТЕ БЕЗ РАЗРЕШЕНИЯ ОХРАНЯЕМЫЕ АВТОРСКИМ ПРАВОМ МАТЕРИАЛЫ!
Отменить
Справка по редактированию
(в новом окне)
Страница, используемая на этой странице:
Модуль:ТранспортнаяСхема/doc
(
править
)
Поиск
Найти
Редактирование:
Модуль:ТранспортнаяСхема
Добавить тему