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

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


-- Извлечение аргументов вызова
-- Извлечение аргументов
local function getArgs(frame)
local function getArgs(frame)
     local args = {}
     local args = {}
Строка 52: Строка 52:
end
end


-- Генерация карты на чистом HTML/CSS
-- Генерация Canvas схемы с поддержкой масштабирования и кадрирования
function p.canvas(frame)
function p.canvas(frame)
     local args = getArgs(frame)
     local args = getArgs(frame)
     local data = loadData(args)
     local data = loadData(args)
      
      
     local mapWidth = tonumber(args['ширина'] or 850) or 850
    -- Базовый размер оригинального холста
     local mapHeight = tonumber(args['высота'] or 620) or 620
     local origW = tonumber(args['ширина_оригинала'] or 850) or 850
     local origH = tonumber(args['высота_оригинала'] or 620) or 620
      
      
     local out = mw.html.create('div')
    -- Параметры кадрирования (вырезки)
         :addClass('ts-html-transport-map')
    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'])
   
    if 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)
   
    -- Внешний контейнер-обрезчик (Viewport)
    local wrapper = mw.html.create('div')
         :addClass('ts-transport-map-viewport')
         :css({
         :css({
             ['position'] = 'relative',
             ['position'] = 'relative',
             ['width'] = mapWidth .. 'px',
             ['width'] = string.format('%.1fpx', finalBoxW),
             ['height'] = mapHeight .. 'px',
             ['height'] = string.format('%.1fpx', finalBoxH),
             ['max-width'] = '100%',
             ['max-width'] = '100%',
            ['overflow'] = 'hidden',
            ['border-radius'] = '8px',
            ['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-color'] = '#ffffff',
             ['background-image'] = 'radial-gradient(#e1e4e8 1px, transparent 1px)',
             ['background-image'] = 'radial-gradient(#e1e4e8 1px, transparent 1px)',
             ['background-size'] = '24px 24px',
             ['background-size'] = '20px 20px',
             ['border'] = '1px solid #d0d7de',
             ['box-shadow'] = args['без_тени'] and 'none' or '0 2px 8px rgba(0,0,0,0.05)',
            ['border-radius'] = '10px',
             ['user-select'] = 'none'
            ['overflow'] = 'hidden',
            ['margin'] = '0 auto',
            ['user-select'] = 'none',
            ['box-shadow'] = '0 3px 10px rgba(0,0,0,0.04)',
             ['font-family'] = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
         })
         })
 
       
     -- Плашка логотипа в левом верхнем углу
     -- Смещение внутри холста
     local header = out:tag('div')
     local shiftX = cropX or 0
    local shiftY = cropY or 0
   
    -- Внутренний трансформируемый холст
    local canvas = wrapper:tag('div')
        :addClass('ts-html-transport-canvas')
         :css({
         :css({
             ['position'] = 'absolute',
             ['position'] = 'absolute',
             ['top'] = '16px',
             ['left'] = string.format('%.1fpx', -shiftX * scale),
            ['left'] = '18px',
             ['top'] = string.format('%.1fpx', -shiftY * scale),
             ['display'] = 'flex',
             ['width'] = origW .. 'px',
            ['align-items'] = 'center',
             ['height'] = origH .. 'px',
             ['gap'] = '10px',
             ['transform'] = string.format('scale(%.4f)', scale),
             ['z-index'] = '5',
             ['transform-origin'] = '0 0',
             ['background'] = 'rgba(255,255,255,0.92)',
             ['font-family'] = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
             ['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',
    if not (args['шапка'] == 'нет' or cropX ~= nil) then
            ['height'] = '24px',
        local header = canvas:tag('div')
            ['background'] = '#ED1B24',
            :css({
            ['color'] = '#fff',
                ['position'] = 'absolute',
            ['font-weight'] = 'bold',
                ['top'] = '16px',
            ['font-size'] = '13px',
                ['left'] = '18px',
            ['display'] = 'flex',
                ['display'] = 'flex',
            ['align-items'] = 'center',
                ['align-items'] = 'center',
            ['justify-content'] = 'center',
                ['gap'] = '10px',
            ['border-radius'] = '50%'
                ['z-index'] = '5',
        }):wikitext('M')
                ['background'] = 'rgba(255,255,255,0.92)',
    header:tag('div')
                ['padding'] = '6px 12px',
        :css({['text-align'] = 'left'})
                ['border-radius'] = '8px',
        :tag('div'):css({['font-weight'] = 'bold', ['font-size'] = '13px', ['color'] = '#1f2328'}):wikitext(data.title or 'Муроморский скоростной транспорт'):done()
                ['border'] = '1px solid #e1e4e8',
        :tag('div'):css({['font-size'] = '10px', ['color'] = '#57606a', ['text-transform'] = 'uppercase', ['letter-spacing'] = '0.5px'}):wikitext('Интерактивная схема линий'):done()
                ['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 data.lines then
     if data.lines then
Строка 131: Строка 185:
                     local isFuture = (st1.future and st2.future)
                     local isFuture = (st1.future and st2.future)
                      
                      
                     local track = out:tag('div')
                     canvas:tag('div')
                         :css({
                         :css({
                             ['position'] = 'absolute',
                             ['position'] = 'absolute',
Строка 162: Строка 216:
                      
                      
                     -- Кружок станции
                     -- Кружок станции
                     local node = out:tag('div')
                     local node = canvas:tag('div')
                         :css({
                         :css({
                             ['position'] = 'absolute',
                             ['position'] = 'absolute',
Строка 181: Строка 235:
                     -- Текстовая подпись станции
                     -- Текстовая подпись станции
                     local isRightSide = (x >= 430)
                     local isRightSide = (x >= 430)
                     local label = out:tag('div')
                     local label = canvas:tag('div')
                         :css({
                         :css({
                             ['position'] = 'absolute',
                             ['position'] = 'absolute',
Строка 202: Строка 256:
     end
     end


     return tostring(out)
     return tostring(wrapper)
end
end


return p
return p