Модуль:ТранспортнаяСхема: различия между версиями
Tringoli (обсуждение | вклад) мНет описания правки |
Tringoli (обсуждение | вклад) мНет описания правки |
||
| Строка 1: | Строка 1: | ||
local p = {} | local p = {} | ||
-- Извлечение аргументов | -- Извлечение аргументов | ||
local function getArgs(frame) | local function getArgs(frame) | ||
local args = {} | local args = {} | ||
| Строка 52: | Строка 52: | ||
end | end | ||
-- Генерация | -- Генерация 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 | -- Базовый размер оригинального холста | ||
local | local origW = tonumber(args['ширина_оригинала'] or 850) or 850 | ||
local origH = tonumber(args['высота_оригинала'] or 620) or 620 | |||
local | -- Параметры кадрирования (вырезки) | ||
:addClass('ts | 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'] = | ['width'] = string.format('%.1fpx', finalBoxW), | ||
['height'] = | ['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'] = ' | ['background-size'] = '20px 20px', | ||
[' | ['box-shadow'] = args['без_тени'] and 'none' or '0 2px 8px rgba(0,0,0,0.05)', | ||
['user-select'] = 'none' | |||
[' | |||
}) | }) | ||
-- | -- Смещение внутри холста | ||
local | 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', | ||
[' | ['left'] = string.format('%.1fpx', -shiftX * scale), | ||
['top'] = string.format('%.1fpx', -shiftY * scale), | |||
[' | ['width'] = origW .. 'px', | ||
['height'] = origH .. 'px', | |||
[' | ['transform'] = string.format('scale(%.4f)', scale), | ||
[' | ['transform-origin'] = '0 0', | ||
[' | ['font-family'] = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" | ||
[' | |||
[' | |||
}) | }) | ||
header:tag('div') | |||
:css({ | -- Шапка/логотип схемы (отключается параметром шапка=нет или при кадрировании) | ||
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 data.lines then | if data.lines then | ||
| Строка 131: | Строка 185: | ||
local isFuture = (st1.future and st2.future) | local isFuture = (st1.future and st2.future) | ||
canvas:tag('div') | |||
:css({ | :css({ | ||
['position'] = 'absolute', | ['position'] = 'absolute', | ||
| Строка 162: | Строка 216: | ||
-- Кружок станции | -- Кружок станции | ||
local node = | local node = canvas:tag('div') | ||
:css({ | :css({ | ||
['position'] = 'absolute', | ['position'] = 'absolute', | ||
| Строка 181: | Строка 235: | ||
-- Текстовая подпись станции | -- Текстовая подпись станции | ||
local isRightSide = (x >= 430) | local isRightSide = (x >= 430) | ||
local label = | local label = canvas:tag('div') | ||
:css({ | :css({ | ||
['position'] = 'absolute', | ['position'] = 'absolute', | ||
| Строка 202: | Строка 256: | ||
end | end | ||
return tostring( | return tostring(wrapper) | ||
end | end | ||
return p | return p | ||