Модуль:ТранспортнаяСхема: различия между версиями
Tringoli (обсуждение | вклад) мНет описания правки |
Tringoli (обсуждение | вклад) мНет описания правки Метка: ручная отмена |
||
| Строка 94: | Строка 94: | ||
end | end | ||
-- Генерация интерактивной Canvas-схемы с | -- Генерация интерактивной Canvas-схемы с панорамированием и управлением | ||
function p.canvas(frame) | function p.canvas(frame) | ||
local args = getArgs(frame) | local args = getArgs(frame) | ||
| Строка 102: | Строка 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 | ||
| Строка 123: | Строка 123: | ||
local boxW = tonumber(args['ширина_блока'] or args['ширина_карточки'] or args['ширина']) | local boxW = tonumber(args['ширина_блока'] or args['ширина_карточки'] or args['ширина']) | ||
local scale = tonumber(args['масштаб'] or args['scale | local scale = tonumber(args['масштаб'] or args['scale']) | ||
local isScrollMode = (args['прокрутка'] == 'да' or args['скролл'] == 'да' or args['scroll'] == 'yes') | local isScrollMode = (args['прокрутка'] == 'да' or args['скролл'] == 'да' or args['scroll'] == 'yes') | ||
if not isScrollMode and boxW and not scale then | if not isScrollMode and boxW and not scale then | ||
| Строка 134: | Строка 133: | ||
end | end | ||
local finalBoxW = boxW or (targetW * | local finalBoxW = boxW or (targetW * scale) | ||
local finalBoxH = tonumber(args['высота_блока'] or args['высота_карточки'] or args['высота']) or (targetH * | local finalBoxH = tonumber(args['высота_блока'] or args['высота_карточки'] or args['высота']) or (targetH * scale) | ||
-- Внешний контейнер-вьюпорт с поддержкой перемещения клавиатурой и скролла | |||
local wrapper = mw.html.create('div') | |||
-- Внешний контейнер- | |||
local | |||
:addClass('ts-transport-map-viewport') | :addClass('ts-transport-map-viewport') | ||
:attr('tabindex', '0') | :attr('tabindex', '0') | ||
:attr('title', ' | :attr('title', 'Кликните для управления стрелками клавиатуры (← ↑ → ↓) или скролла мышью') | ||
:css({ | :css({ | ||
['position'] = 'relative', | ['position'] = 'relative', | ||
['width'] = ' | ['width'] = string.format('%.1fpx', finalBoxW), | ||
['height'] = string.format('%.1fpx', finalBoxH), | ['height'] = string.format('%.1fpx', finalBoxH), | ||
['max-width'] = '100%', | |||
['overflow'] = isScrollMode and 'auto' or 'hidden', | ['overflow'] = isScrollMode and 'auto' or 'hidden', | ||
['scroll-behavior'] = 'smooth', | ['scroll-behavior'] = 'smooth', | ||
| Строка 210: | Строка 152: | ||
['border-radius'] = '10px', | ['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', | |||
['background-color'] = '#ffffff', | ['background-color'] = '#ffffff', | ||
['background-image'] = 'radial-gradient(#e1e4e8 1px, transparent 1px)', | ['background-image'] = 'radial-gradient(#e1e4e8 1px, transparent 1px)', | ||
| Строка 215: | Строка 158: | ||
['box-shadow'] = args['без_тени'] and 'none' or '0 2px 8px rgba(0,0,0,0.06)', | ['box-shadow'] = args['без_тени'] and 'none' or '0 2px 8px rgba(0,0,0,0.06)', | ||
['outline'] = 'none', | ['outline'] = 'none', | ||
['user-select'] = 'none' | ['user-select'] = 'none' | ||
}) | }) | ||
| Строка 222: | Строка 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') | ||
| Строка 229: | Строка 171: | ||
['left'] = not isScrollMode and string.format('%.1fpx', -shiftX * scale) or '0', | ['left'] = not isScrollMode and string.format('%.1fpx', -shiftX * scale) or '0', | ||
['top'] = not isScrollMode and string.format('%.1fpx', -shiftY * scale) or '0', | ['top'] = not isScrollMode and string.format('%.1fpx', -shiftY * scale) or '0', | ||
['width'] = | ['width'] = origW .. 'px', | ||
['height'] = | ['height'] = origH .. 'px', | ||
['transform'] = (scale ~= 1.0 and not isScrollMode) and string.format('scale(%.4f)', scale) or 'none', | ['transform'] = (scale ~= 1.0 and not isScrollMode) and string.format('scale(%.4f)', scale) or 'none', | ||
['transform-origin'] = '0 0', | ['transform-origin'] = '0 0', | ||
| Строка 272: | Строка 214: | ||
end | 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 | if data.lines then | ||
-- 1. Слой путей | -- 1. Слой путей | ||
for _, line in ipairs(data.lines) do | for _, line in ipairs(data.lines) do | ||
local color = line.color or '#888888' | local color = line.color or '#888888' | ||
| Строка 282: | Строка 247: | ||
for i = 1, #stations - 1 do | for i = 1, #stations - 1 do | ||
local st1, st2 = stations[i], stations[i+1] | local st1, st2 = stations[i], stations[i+1] | ||
local | local x1, y1 = parseCoords(st1.coords) | ||
local | local x2, y2 = parseCoords(st2.coords) | ||
if | if x1 and y1 and x2 and y2 then | ||
local dx = x2 - x1 | local dx = x2 - x1 | ||
local dy = y2 - y1 | local dy = y2 - y1 | ||
| Строка 294: | Строка 256: | ||
local angle = math.deg(math.atan2(dy, dx)) | local angle = math.deg(math.atan2(dy, dx)) | ||
local isFuture = (st1.future and st2.future) | local isFuture = (st1.future and st2.future) | ||
canvas:tag('div') | canvas:tag('div') | ||
| Строка 300: | Строка 261: | ||
['position'] = 'absolute', | ['position'] = 'absolute', | ||
['left'] = x1 .. 'px', | ['left'] = x1 .. 'px', | ||
['top'] = (y1 - | ['top'] = (y1 - 3) .. 'px', | ||
['width'] = length .. 'px', | ['width'] = length .. 'px', | ||
['height'] = isFuture and '0px' or | ['height'] = isFuture and '0px' or '6px', | ||
['background-color'] = isFuture and 'transparent' or color, | ['background-color'] = isFuture and 'transparent' or color, | ||
['border-top'] = isFuture and ( | ['border-top'] = isFuture and ('5px dashed ' .. color) or 'none', | ||
['transform-origin'] = '0 50%', | ['transform-origin'] = '0 50%', | ||
['transform'] = 'rotate(' .. angle .. 'deg)', | ['transform'] = 'rotate(' .. angle .. 'deg)', | ||
| Строка 320: | Строка 281: | ||
for _, st in ipairs(line.stations or {}) do | for _, st in ipairs(line.stations or {}) do | ||
local | local x, y = parseCoords(st.coords) | ||
if | if x and y then | ||
local name = st.name or st.article or '' | local name = st.name or st.article or '' | ||
local article = st.article or name | local article = st.article or name | ||
local isFuture = (st.future == true) | local isFuture = (st.future == true) | ||
local node = canvas:tag('div') | local node = canvas:tag('div') | ||
:css({ | :css({ | ||
['position'] = 'absolute', | ['position'] = 'absolute', | ||
['left'] = (x - | ['left'] = (x - 7) .. 'px', | ||
['top'] = (y - | ['top'] = (y - 7) .. 'px', | ||
['width'] = | ['width'] = '14px', | ||
['height'] = | ['height'] = '14px', | ||
['border-radius'] = '50%', | ['border-radius'] = '50%', | ||
['background-color'] = '#ffffff', | ['background-color'] = '#ffffff', | ||
| Строка 346: | Строка 304: | ||
node:wikitext('[[' .. article .. '|<span style="display:block;width:100%;height:100%;" title="' .. name .. '"></span>]]') | node:wikitext('[[' .. article .. '|<span style="display:block;width:100%;height:100%;" title="' .. name .. '"></span>]]') | ||
local isRightSide = (x >= (origW / 2)) | |||
local isRightSide = ( | |||
local label = canvas:tag('div') | local label = canvas:tag('div') | ||
:css({ | :css({ | ||
['position'] = 'absolute', | ['position'] = 'absolute', | ||
['left'] = isRightSide and ((x + | ['left'] = isRightSide and ((x + 11) .. 'px') or ((x - 11) .. 'px'), | ||
['top'] = y .. 'px', | ['top'] = y .. 'px', | ||
['transform'] = isRightSide and 'translateY(-50%)' or 'translate(-100%, -50%)', | ['transform'] = isRightSide and 'translateY(-50%)' or 'translate(-100%, -50%)', | ||
['text-align'] = isRightSide and 'left' or 'right', | ['text-align'] = isRightSide and 'left' or 'right', | ||
['white-space'] = 'nowrap', | ['white-space'] = 'nowrap', | ||
['font-size'] = | ['font-size'] = '11.5px', | ||
['font-weight'] = '500', | ['font-weight'] = '500', | ||
['z-index'] = '4', | ['z-index'] = '4', | ||
| Строка 372: | Строка 326: | ||
end | end | ||
return tostring( | return tostring(wrapper) | ||
end | end | ||
return p | return p | ||