Заглавная
Случайная
Войти
Настройки
О MURS вики
Отказ от ответственности
Найти
Редактирование:
Модуль:ТранспортнаяСхема
Внимание:
Вы не вошли в систему. Ваш 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
(
править
)