Перейти к содержанию

Модуль:ТранспортнаяСхема: различия между версиями

Материал из MURS вики
мНет описания правки
мНет описания правки
Строка 1: Строка 1:
local p = {}
local p = {}


-- Безопасная загрузка JSON из пространства Шаблонов
-- Универсальное слияние аргументов (из инфобокса, шаблона и вызова)
local function loadData(frame)
local function getArgs(frame)
     local dataParam = frame.args['данные'] or frame.args[1]
    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 loadData(args)
     local dataParam = args['данные'] or args[1]
     if not dataParam or dataParam == '' then
     if not dataParam or dataParam == '' then
         error('Не указано имя базы данных или страница JSON (параметр 1 или |данные=)')
         error('Не указано имя базы данных или страница JSON (параметр 1 или |данные=)')
Строка 13: Строка 28:
     end
     end
      
      
    -- Способ 1: mw.loadJsonData
     local success, jsonData = pcall(mw.loadJsonData, dataPageTitle)
     local success, jsonData = pcall(mw.loadJsonData, dataPageTitle)
     if success and jsonData then
     if success and jsonData then
Строка 19: Строка 33:
     end
     end
      
      
    -- Способ 2: Резервная загрузка текста
     local titleObj = mw.title.new(dataPageTitle)
     local titleObj = mw.title.new(dataPageTitle)
     if titleObj and titleObj.exists then
     if titleObj and titleObj.exists then
Строка 32: Строка 45:
                 return decodedData
                 return decodedData
             else
             else
                 error('Ошибка в синтаксисе JSON на странице [[' .. dataPageTitle .. ']]! Проверьте запятые и кавычки.')
                 error('Ошибка в синтаксисе JSON на странице [[' .. dataPageTitle .. ']]!')
             end
             end
         end
         end
     end
     end
      
      
     error('Страница данных [[' .. dataPageTitle .. ']] не найдена. Убедитесь, что она создана.')
     error('Страница данных [[' .. dataPageTitle .. ']] не найдена.')
end
end


-- Генерация кода <imagemap>
-- Генерация кода <imagemap> с учётом ширины
local function buildImageMap(jsonData, imageFile, width, defaultRadius)
local function buildImageMap(jsonData, imageFile, width, defaultRadius)
     local radius = defaultRadius or '16'
     local radius = defaultRadius or '16'
     local mapText = 'Файл:' .. imageFile .. '|' .. width .. '|center|alt=' .. (jsonData.title or 'Схема скоростного транспорта Муроморска') .. '\n'
     local mapText = 'Файл:' .. imageFile .. '|' .. width .. '|center|alt=' .. (jsonData.title or 'Схема транспорта') .. '\n'
      
      
     for _, line in ipairs(jsonData.lines or {}) do
     for _, line in ipairs(jsonData.lines or {}) do
Строка 62: Строка 75:
end
end


-- Названия категорий транспорта
local function getModeLabel(mode)
local function getModeLabel(mode)
     local modes = {
     local modes = {
Строка 72: Строка 84:
end
end


-- Рендеринг схемы
function p.render(frame)
function p.render(frame)
     local args = frame:getParent().args
     local args = getArgs(frame)
    if not args['данные'] and not frame.args['данные'] then
     local jsonData = loadData(args)
        args = frame.args
    end
 
     local jsonData = loadData(frame)
    local isInfobox = (args['карточка'] == 'да' or args['тип'] == 'карточка' or frame.args['карточка'] == 'да')
      
      
    local isInfobox = (args['карточка'] == 'да' or args['тип'] == 'карточка')
     local defaultWidth = isInfobox and '280px' or '850px'
     local defaultWidth = isInfobox and '280px' or '850px'
   
    -- Ширина теперь строго приоритезирует параметр вызова, затем режим карточки
    local width = args['ширина'] or (isInfobox and '280px') or jsonData.width or '850px'
     local imageFile = args['файл'] or jsonData.image or 'Схема_Муроморского_метрополитена.png'
     local imageFile = args['файл'] or jsonData.image or 'Схема_Муроморского_метрополитена.png'
    local width = args['ширина'] or (not isInfobox and jsonData.width) or defaultWidth
     local caption = args['подпись'] or jsonData.caption or ''
     local caption = args['подпись'] or jsonData.caption or ''
     local defaultRadius = args['радиус'] or jsonData.defaultRadius or '16'
     local defaultRadius = args['радиус'] or jsonData.defaultRadius or '16'
Строка 97: Строка 106:
         })
         })


     -- 1. Карточка карты с imagemap
     -- Контейнер карты
     local mapCard = root:tag('div')
     local mapCard = root:tag('div')
         :addClass('ts-transport-map-card')
         :addClass('ts-transport-map-card')
Строка 132: Строка 141:
     end
     end


     -- В режиме карточки (инфобокса) скрываем громоздкую легенду и реестр
     -- Легенда и список выводятся только вне карточки инфобокса
     if not isInfobox then
     if not isInfobox then
        -- 2. Легенда линий
         if jsonData.lines and #jsonData.lines > 0 then
         if jsonData.lines and #jsonData.lines > 0 then
             local legend = root:tag('div')
             local legend = root:tag('div')
Строка 153: Строка 161:
                     ['color'] = '#57606a',
                     ['color'] = '#57606a',
                     ['text-transform'] = 'uppercase',
                     ['text-transform'] = 'uppercase',
                     ['margin-bottom'] = '6px',
                     ['margin-bottom'] = '6px'
                    ['letter-spacing'] = '0.5px'
                 })
                 })
                 :wikitext(jsonData.legendTitle or 'Линии скоростного транспорта')
                 :wikitext(jsonData.legendTitle or 'Линии скоростного транспорта')
Строка 200: Строка 207:
         end
         end


        -- 3. Каталог станций
         if jsonData.lines and #jsonData.lines > 0 then
         if jsonData.lines and #jsonData.lines > 0 then
             local listCard = root:tag('div')
             local listCard = root:tag('div')
Строка 209: Строка 215:
                     ['text-align'] = 'left',
                     ['text-align'] = 'left',
                     ['background'] = 'var(--background-color-neutral-subtle, #f8f9fa)',
                     ['background'] = 'var(--background-color-neutral-subtle, #f8f9fa)',
                     ['border'] = '1px solid var(--border-color-base, #d0d7de)',
                     ['border'] = '1px solid #d0d7de',
                     ['border-radius'] = '8px',
                     ['border-radius'] = '8px',
                     ['overflow'] = 'hidden'
                     ['overflow'] = 'hidden'
Строка 220: Строка 226:
                     ['background'] = 'linear-gradient(180deg, #f6f8fa 0%, #eaeef2 100%)',
                     ['background'] = 'linear-gradient(180deg, #f6f8fa 0%, #eaeef2 100%)',
                     ['padding'] = '9px 14px',
                     ['padding'] = '9px 14px',
                     ['border-bottom'] = '1px solid var(--border-color-base, #d0d7de)'
                     ['border-bottom'] = '1px solid #d0d7de'
                 })
                 })
                 :wikitext(jsonData.listTitle or '📋 Реестр станций метрополитена и МЖД')
                 :wikitext(jsonData.listTitle or '📋 Реестр станций')


             local listContent = listCard:tag('div')
             local listContent = listCard:tag('div')

Версия от 12:31, 22 августа 2026

Для документации этого модуля может быть создана страница Модуль:ТранспортнаяСхема/doc

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 loadData(args)
    local dataParam = args['данные'] or args[1]
    if not dataParam or dataParam == '' then
        error('Не указано имя базы данных или страница JSON (параметр 1 или |данные=)')
    end
    
    local dataPageTitle = dataParam
    if not mw.ustring.find(dataParam, ':') then
        dataPageTitle = 'Шаблон:Интерактивная схема ' .. dataParam .. '/data.json'
    end
    
    local success, jsonData = pcall(mw.loadJsonData, dataPageTitle)
    if success and jsonData then
        return jsonData
    end
    
    local titleObj = mw.title.new(dataPageTitle)
    if titleObj and titleObj.exists then
        local rawText = titleObj:getContent()
        if rawText and rawText ~= '' then
            rawText = mw.ustring.gsub(rawText, '<noinclude>.-</noinclude>', '')
            rawText = mw.ustring.gsub(rawText, '<!--.--->', '')
            rawText = mw.text.trim(rawText)
            
            local decodeSuccess, decodedData = pcall(mw.text.jsonDecode, rawText)
            if decodeSuccess and decodedData then
                return decodedData
            else
                error('Ошибка в синтаксисе JSON на странице [[' .. dataPageTitle .. ']]!')
            end
        end
    end
    
    error('Страница данных [[' .. dataPageTitle .. ']] не найдена.')
end

-- Генерация кода <imagemap> с учётом ширины
local function buildImageMap(jsonData, imageFile, width, defaultRadius)
    local radius = defaultRadius or '16'
    local mapText = 'Файл:' .. imageFile .. '|' .. width .. '|center|alt=' .. (jsonData.title or 'Схема транспорта') .. '\n'
    
    for _, line in ipairs(jsonData.lines or {}) do
        if line.box and line.article then
            mapText = mapText .. 'rect ' .. line.box .. ' [[' .. line.article .. '|' .. (line.name or line.article) .. ']]\n'
        end
        
        for _, station in ipairs(line.stations or {}) do
            if station.coords and station.article then
                local stRadius = station.radius or radius
                mapText = mapText .. 'circle ' .. station.coords .. ' ' .. stRadius .. ' [[' .. station.article .. '|' .. (station.name or station.article) .. ']]\n'
            end
        end
    end
    
    mapText = mapText .. 'desc none\n'
    return mapText
end

local function getModeLabel(mode)
    local modes = {
        metro = '🚇 Линии Муроморского метрополитена',
        train = '🚆 Направления Мурской железной дороги (МЖД)',
        other = '🚍 Прочие линии'
    }
    return modes[mode] or modes.other
end

function p.render(frame)
    local args = getArgs(frame)
    local jsonData = loadData(args)
    
    local isInfobox = (args['карточка'] == 'да' or args['тип'] == 'карточка')
    local defaultWidth = isInfobox and '280px' or '850px'
    
    -- Ширина теперь строго приоритезирует параметр вызова, затем режим карточки
    local width = args['ширина'] or (isInfobox and '280px') or jsonData.width or '850px'
    local imageFile = args['файл'] or jsonData.image or 'Схема_Муроморского_метрополитена.png'
    local caption = args['подпись'] or jsonData.caption or ''
    local defaultRadius = args['радиус'] or jsonData.defaultRadius or '16'
    
    local root = mw.html.create('div')
        :addClass('ts-transport-scheme')
        :css({
            ['clear'] = isInfobox and 'none' or 'both',
            ['margin'] = isInfobox and '0 auto' or '1.2em auto',
            ['width'] = '100%',
            ['text-align'] = 'center'
        })

    -- Контейнер карты
    local mapCard = root:tag('div')
        :addClass('ts-transport-map-card')
        :css({
            ['display'] = isInfobox and 'block' or 'inline-block',
            ['max-width'] = '100%',
            ['background'] = isInfobox and 'transparent' or 'var(--background-color-base, #ffffff)',
            ['border'] = isInfobox and 'none' or '1px solid var(--border-color-base, #d0d7de)',
            ['border-radius'] = isInfobox and '0' or '10px',
            ['padding'] = isInfobox and '0' or '10px',
            ['box-shadow'] = isInfobox and 'none' or '0 3px 12px rgba(0,0,0,0.06)'
        })

    local mapContainer = mapCard:tag('div')
        :css({
            ['overflow-x'] = 'auto',
            ['max-width'] = '100%',
            ['text-align'] = 'center'
        })

    local imageMapContent = buildImageMap(jsonData, imageFile, width, defaultRadius)
    mapContainer:wikitext(frame:preprocess('{{#tag:imagemap|' .. imageMapContent .. '}}'))

    if caption ~= '' and not isInfobox then
        mapCard:tag('div')
            :css({
                ['font-size'] = '12px',
                ['color'] = '#57606a',
                ['margin-top'] = '8px',
                ['padding-top'] = '6px',
                ['border-top'] = '1px dashed var(--border-color-base, #e1e4e8)'
            })
            :wikitext('🗺 ' .. caption)
    end

    -- Легенда и список выводятся только вне карточки инфобокса
    if not isInfobox then
        if jsonData.lines and #jsonData.lines > 0 then
            local legend = root:tag('div')
                :addClass('ts-transport-lines-legend')
                :css({
                    ['margin'] = '12px auto',
                    ['max-width'] = '950px',
                    ['padding'] = '8px 12px',
                    ['background'] = '#f6f8fa',
                    ['border'] = '1px solid #d0d7de',
                    ['border-radius'] = '8px'
                })

            legend:tag('div')
                :css({
                    ['font-size'] = '11px',
                    ['font-weight'] = 'bold',
                    ['color'] = '#57606a',
                    ['text-transform'] = 'uppercase',
                    ['margin-bottom'] = '6px'
                })
                :wikitext(jsonData.legendTitle or 'Линии скоростного транспорта')

            local legendBadges = legend:tag('div')
                :css({
                    ['display'] = 'flex',
                    ['flex-wrap'] = 'wrap',
                    ['justify-content'] = 'center',
                    ['gap'] = '5px'
                })

            for _, line in ipairs(jsonData.lines) do
                local badge = legendBadges:tag('span')
                    :css({
                        ['display'] = 'inline-flex',
                        ['align-items'] = 'center',
                        ['padding'] = '2px 8px 2px 4px',
                        ['background'] = '#ffffff',
                        ['border'] = '1px solid #d0d7de',
                        ['border-radius'] = '20px',
                        ['font-size'] = '12px'
                    })

                badge:tag('span')
                    :css({
                        ['display'] = 'inline-flex',
                        ['align-items'] = 'center',
                        ['justify-content'] = 'center',
                        ['min-width'] = '16px',
                        ['height'] = '16px',
                        ['padding'] = '0 4px',
                        ['border-radius'] = '999px',
                        ['background-color'] = line.color or '#888888',
                        ['color'] = line.textColor or '#ffffff',
                        ['font-size'] = '9.5px',
                        ['font-weight'] = 'bold',
                        ['margin-right'] = '6px'
                    })
                    :wikitext(line.symbol or line.number or '•')

                badge:tag('span')
                    :wikitext('[[' .. (line.article or '') .. '|' .. (line.displayName or line.name or line.article or '') .. ']]')
            end
        end

        if jsonData.lines and #jsonData.lines > 0 then
            local listCard = root:tag('div')
                :addClass('mw-collapsible toccolours')
                :css({
                    ['margin'] = '12px auto 0 auto',
                    ['max-width'] = '950px',
                    ['text-align'] = 'left',
                    ['background'] = 'var(--background-color-neutral-subtle, #f8f9fa)',
                    ['border'] = '1px solid #d0d7de',
                    ['border-radius'] = '8px',
                    ['overflow'] = 'hidden'
                })

            listCard:tag('div')
                :css({
                    ['font-weight'] = 'bold',
                    ['font-size'] = '13.5px',
                    ['background'] = 'linear-gradient(180deg, #f6f8fa 0%, #eaeef2 100%)',
                    ['padding'] = '9px 14px',
                    ['border-bottom'] = '1px solid #d0d7de'
                })
                :wikitext(jsonData.listTitle or '📋 Реестр станций')

            local listContent = listCard:tag('div')
                :addClass('mw-collapsible-content')
                :css({
                    ['padding'] = '14px 18px',
                    ['column-width'] = '20em',
                    ['column-gap'] = '2em',
                    ['column-rule'] = '1px solid #e1e4e8',
                    ['font-size'] = '13px',
                    ['line-height'] = '1.7'
                })

            local currentMode = nil
            local listWikitext = ''

            for _, line in ipairs(jsonData.lines) do
                local mode = line.mode or 'other'
                if mode ~= currentMode then
                    currentMode = mode
                    listWikitext = listWikitext .. "\n; " .. getModeLabel(mode) .. "\n"
                end

                listWikitext = listWikitext .. "* '''[[" .. (line.article or '') .. '|' .. (line.name or line.article or '') .. "]]'''\n"
                for _, station in ipairs(line.stations or {}) do
                    local badge = "<span style='display:inline-flex;align-items:center;justify-content:center;min-width:14px;height:14px;padding:0 3px;border-radius:999px;background:" .. (line.color or '#888') .. ";color:" .. (line.textColor or '#fff') .. ";font-size:9px;font-weight:bold;margin-right:4px;'>" .. (line.symbol or line.number or '•') .. "</span>"
                    listWikitext = listWikitext .. '** ' .. badge .. ' [[' .. station.article .. '|' .. (station.name or station.article) .. ']]'
                    if station.transfer then
                        listWikitext = listWikitext .. " <small style='color:#57606a;'>(" .. station.transfer .. ")</small>"
                    end
                    if station.future then
                        listWikitext = listWikitext .. " <small style='color:#8c959f; font-style:italic;'>(строит.)</small>"
                    end
                    listWikitext = listWikitext .. '\n'
                end
            end

            listContent:wikitext(listWikitext)
        end
    end

    return tostring(root)
end

return p