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

мНет описания правки
Нет описания правки
Строка 25: Строка 25:
end
end


-- Загрузка данных из data.json с проверкой всех пространств имён
-- Загрузка данных из data.json
local function loadData(args)
local function loadData(args)
     local dataParam = args['данные'] or args[1] or 'Муроморского'
     local dataParam = args['данные'] or args[1] or 'Муроморского'
     dataParam = mw.text.trim(dataParam)
     dataParam = mw.text.trim(dataParam)
      
      
    -- Очищаем имя от префиксов для создания комбинаций
     local cleanName = mw.ustring.gsub(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, '^[Tt][Ee][Mm][Pp][Ll][Aa][Tt][Ee]:', '')
Строка 48: Строка 47:
     end
     end
      
      
    -- Добавляем исходный путь
     addCandidate(dataParam)
     addCandidate(dataParam)
   
    -- Варианты в пространстве Шаблон:
     addCandidate('Шаблон:Интерактивная схема ' .. cleanName .. '/data.json')
     addCandidate('Шаблон:Интерактивная схема ' .. cleanName .. '/data.json')
     addCandidate('Шаблон:Интерактивная схема ' .. cleanName .. ' метрополитена/data.json')
     addCandidate('Шаблон:Интерактивная схема ' .. cleanName .. ' метрополитена/data.json')
     addCandidate('Шаблон:' .. cleanName .. '/data.json')
     addCandidate('Шаблон:' .. cleanName .. '/data.json')
     addCandidate('Шаблон:' .. cleanName)
     addCandidate('Шаблон:' .. cleanName)
   
    -- Варианты в основном пространстве (без Шаблон:)
     addCandidate('Интерактивная схема ' .. cleanName .. '/data.json')
     addCandidate('Интерактивная схема ' .. cleanName .. '/data.json')
     addCandidate('Интерактивная схема ' .. cleanName .. ' метрополитена/data.json')
     addCandidate('Интерактивная схема ' .. cleanName .. ' метрополитена/data.json')
     addCandidate(cleanName .. '/data.json')
     addCandidate(cleanName .. '/data.json')
     addCandidate(cleanName)
     addCandidate(cleanName)
   
    -- Англоязычный алиас
    addCandidate('Template:Интерактивная схема ' .. cleanName .. '/data.json')


     local jsonErrors = {}
     local jsonErrors = {}
      
      
     for _, pageTitle in ipairs(candidates) do
     for _, pageTitle in ipairs(candidates) do
        -- 1. Попытка загрузки через mw.loadJsonData
         local ok, data = pcall(mw.loadJsonData, pageTitle)
         local ok, data = pcall(mw.loadJsonData, pageTitle)
         if ok and type(data) == 'table' then
         if ok and type(data) == 'table' then
Строка 75: Строка 65:
         end
         end
          
          
        -- 2. Попытка прямого чтения текста страницы
         local titleObj = mw.title.new(pageTitle)
         local titleObj = mw.title.new(pageTitle)
         if titleObj then
         if titleObj then
Строка 85: Строка 74:
                     return decoded
                     return decoded
                 else
                 else
                     table.insert(jsonErrors, '[[' .. pageTitle .. ']] (ошибка синтаксиса: ' .. tostring(decoded) .. ')')
                     table.insert(jsonErrors, '[[' .. pageTitle .. ']] (' .. tostring(decoded) .. ')')
                 end
                 end
             end
             end
Строка 92: Строка 81:
      
      
     if #jsonErrors > 0 then
     if #jsonErrors > 0 then
         error('Найдена страница с ошибкой в синтаксисе JSON:\n* ' .. table.concat(jsonErrors, '\n* '))
         error('Ошибка в синтаксисе JSON:\n* ' .. table.concat(jsonErrors, '\n* '))
     end
     end
      
      
Строка 105: Строка 94:
end
end


-- Генерация Canvas схемы
-- Генерация интерактивной Canvas-схемы с панорамированием и управлением
function p.canvas(frame)
function p.canvas(frame)
     local args = getArgs(frame)
     local args = getArgs(frame)
Строка 113: Строка 102:
     local origH = tonumber(args['высота_оригинала'] or 620) or 620
     local origH = tonumber(args['высота_оригинала'] or 620) or 620
      
      
    -- Кадрирование
     local cropX, cropY, cropW, cropH = nil, nil, nil, nil
     local cropX, cropY, cropW, cropH = nil, nil, nil, nil
     if args['кадрирование'] or args['crop'] then
     if args['кадрирование'] or args['crop'] then
Строка 135: Строка 125:
     local scale = tonumber(args['масштаб'] or args['scale'])
     local scale = tonumber(args['масштаб'] or args['scale'])
      
      
     if boxW and not scale then
    local isScrollMode = (args['прокрутка'] == 'да' or args['скролл'] == 'да' or args['scroll'] == 'yes')
   
     if not isScrollMode and boxW and not scale then
         scale = boxW / targetW
         scale = boxW / targetW
     elseif not scale then
     elseif not scale then
Строка 144: Строка 136:
     local finalBoxH = tonumber(args['высота_блока'] or args['высота_карточки'] or args['высота']) or (targetH * scale)
     local finalBoxH = tonumber(args['высота_блока'] or args['высота_карточки'] or args['высота']) or (targetH * scale)
      
      
    -- Внешний контейнер-вьюпорт с поддержкой перемещения клавиатурой и скролла
     local wrapper = mw.html.create('div')
     local wrapper = mw.html.create('div')
         :addClass('ts-transport-map-viewport')
         :addClass('ts-transport-map-viewport')
        :attr('tabindex', '0')
        :attr('title', 'Кликните для управления стрелками клавиатуры (← ↑ → ↓) или скролла мышью')
         :css({
         :css({
             ['position'] = 'relative',
             ['position'] = 'relative',
Строка 151: Строка 146:
             ['height'] = string.format('%.1fpx', finalBoxH),
             ['height'] = string.format('%.1fpx', finalBoxH),
             ['max-width'] = '100%',
             ['max-width'] = '100%',
             ['overflow'] = 'hidden',
             ['overflow'] = isScrollMode and 'auto' or 'hidden',
             ['border-radius'] = '8px',
            ['scroll-behavior'] = 'smooth',
            ['overscroll-behavior'] = 'contain',
            ['-webkit-overflow-scrolling'] = 'touch',
             ['border-radius'] = '10px',
             ['border'] = args['без_рамки'] and 'none' or '1px solid #d0d7de',
             ['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',
             ['margin'] = (args['прижатие'] == 'лево' and '0 auto 0 0') or (args['прижатие'] == 'право' and '0 0 0 auto') or '0 auto',
Строка 158: Строка 156:
             ['background-image'] = 'radial-gradient(#e1e4e8 1px, transparent 1px)',
             ['background-image'] = 'radial-gradient(#e1e4e8 1px, transparent 1px)',
             ['background-size'] = '20px 20px',
             ['background-size'] = '20px 20px',
             ['box-shadow'] = args['без_тени'] and 'none' or '0 2px 8px rgba(0,0,0,0.05)',
             ['box-shadow'] = args['без_тени'] and 'none' or '0 2px 8px rgba(0,0,0,0.06)',
            ['outline'] = 'none',
             ['user-select'] = 'none'
             ['user-select'] = 'none'
         })
         })
Строка 165: Строка 164:
     local shiftY = cropY or 0
     local shiftY = cropY or 0
      
      
    -- Внутренний холст схемы
     local canvas = wrapper:tag('div')
     local canvas = wrapper:tag('div')
         :addClass('ts-html-transport-canvas')
         :addClass('ts-html-transport-canvas')
         :css({
         :css({
             ['position'] = 'absolute',
             ['position'] = isScrollMode and 'relative' or 'absolute',
             ['left'] = string.format('%.1fpx', -shiftX * scale),
             ['left'] = not isScrollMode and string.format('%.1fpx', -shiftX * scale) or '0',
             ['top'] = string.format('%.1fpx', -shiftY * scale),
             ['top'] = not isScrollMode and string.format('%.1fpx', -shiftY * scale) or '0',
             ['width'] = origW .. 'px',
             ['width'] = origW .. 'px',
             ['height'] = origH .. 'px',
             ['height'] = origH .. 'px',
             ['transform'] = string.format('scale(%.4f)', scale),
             ['transform'] = (scale ~= 1.0 and not isScrollMode) and string.format('scale(%.4f)', scale) or 'none',
             ['transform-origin'] = '0 0',
             ['transform-origin'] = '0 0',
             ['font-family'] = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
             ['font-family'] = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
         })
         })


    -- Шапка/логотип
     if not (args['шапка'] == 'нет' or cropX ~= nil) then
     if not (args['шапка'] == 'нет' or cropX ~= nil) then
         local header = canvas:tag('div')
         local header = canvas:tag('div')
Строка 211: Строка 212:
             :tag('div'):css({['font-weight'] = 'bold', ['font-size'] = '13px', ['color'] = '#1f2328'}):wikitext(data.title or 'Скоростной транспорт'):done()
             :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()
             :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
     end