Module:Spatial image viewer
Appearance
![]() | This module uses TemplateStyles: |
Usage
{{#invoke:Spatial image viewer|makeViewer}}
Make an image viewer for when you have multiple images of the same object at different angles. See {{Spatial image viewer}}.
-- Generate a pseudo-3D image viewer
-- If you have a picture of an object from multiple angles this
-- lets users click through them.
-- Similar to User:Bawolff/earth_rotation except using separate images instead of a sprite sheet.
local p = {}
local heightCache
local function getStart( axis, frame )
return frame.args['start'..axis] or '0'
end
local function getButton( frame, imageList, dir )
local buttonText, formula, forId, disableFormula, startHidden
startDisabled = false
if dir == 'up' then
buttonText = frame.args['uptext'] or '↑'
if #imageList < 2 then
return ''
end
forId = 'y'
formula = '(y+1)%' .. (#imageList)
if frame.args['wrapVertical'] == 'false' then
disableFormula = 'ifgreaterorequal(y,' .. (#imageList-1) .. ')'
end
if getStart('Y',frame) == #imageList-1 then
startDisabled = true
end
elseif dir == 'down' then
buttonText = frame.args['downtext'] or '↓'
if #imageList < 2 then
return ''
end
forId = 'y'
formula = '(y-1+'.. #imageList .. ')%' .. (#imageList)
if frame.args['wrapVertical'] == 'false' then
disableFormula = 'ifzero(y)'
end
if getStart('Y',frame) == 0 then
startDisabled = true
end
elseif dir == 'left' then
buttonText = frame.args['lefttext'] or '←'
if #imageList[1] < 2 then
return ''
end
forId = 'x'
formula = '(x+1)%' .. (#imageList[1])
if frame.args['wrapHorizontal'] == 'false' then
disableFormula = 'ifgreaterorequal(x,' .. (#imageList[1]-1) .. ')'
end
if getStart('X',frame) == #imageList[1]-1 then
startDisabled = true
end
elseif dir == 'right' then
buttonText = frame.args['righttext'] or '→'
if #imageList[1] < 2 then
return ''
end
forId = 'x'
formula = '(x-1+'.. #imageList[1] .. ')%' .. (#imageList[1])
if frame.args['wrapHorizontal'] == 'false' then
disableFormula = 'ifzero(x)'
end
if getStart('X',frame) == '0' then
startDisabled = true
end
else
error( "Unrecognized direction" )
end
local button = frame:expandTemplate{
title = 'Calculator button',
args = {
type = 'default',
["for"] = forId,
contents = buttonText,
formula = formula
}
}
if disableFormula ~= nil then
button = frame:expandTemplate{
title = 'calculator-hideifzero',
args = {
text = button,
formula = disableFormula,
starthidden = startDisabled
}
} .. frame:expandTemplate{
title = 'calculator-hideifzero',
args = {
text = frame:expandTemplate{
title = 'Calculator button',
args = {
type = 'default',
contents = buttonText,
disabled = '1'
}
},
formula = 'not(' .. disableFormula .. ')',
starthidden = not startDisabled
}
}
end
return button
end
-- We assume all images have roughly the same aspect ratio
local function getHeight(frame, imageList)
if frame.args['height'] then
return frame.args['height']
end
if heightCache == nil then
local title = mw.title.new( imageList[1][1], 'File' )
assert( title and title.file, "Invalid file")
heightCache = math.ceil((tonumber(frame.args['width']) or 250) * (title.file.height/title.file.width))
end
return heightCache
end
local function parseImageList( images )
local imageList = {}
local rows = mw.text.split( mw.text.trim( images ), "\n+" )
local width, height
for i, v in ipairs( rows ) do
row = mw.text.split( mw.text.trim( v ), "%s+" )
imageList[#imageList+1] = row
if width == nil then
width = #row
assert( width >= 1, "Must have at least one column of images" )
else
assert( width == #row, "Must have a square matrix of images" )
end
end
assert( #imageList >= 1, "Must have at least one row of images" )
return imageList
end
local function getImages(frame, imageList)
local html = mw.html.create( 'div' )
html
:css( 'width', (frame.args['width'] or 250) .. 'px' )
:css( 'height', getHeight(frame, imageList) .. 'px' )
:css( 'position', 'relative' )
for imgRowNumb, imgRow in ipairs( imageList ) do
for imgColNumb, img in ipairs( imgRow ) do
local zDefault = 0
if imgColNumb-1 == getStart('X',frame) and imgRowNumb-1 == getStart('Y',frame) then
zDefault = 1
end
local formula = 'ifequal(x,' .. imgColNumb .. ','
.. 'ifequal(y,' .. imgRowNumb .. '))'
html:tag( 'div' )
:css( 'position', 'absolute' )
:css( 'background', '#fff' )
:css( 'z-index', 'var( --calculator-x' .. (imgColNumb-1) .. 'y' .. (imgRowNumb-1) .. ',' .. zDefault .. ')')
:wikitext( frame:preprocess(
'[[File:' .. img .. '|' .. (frame.args['width'] or 250) .. 'x' .. getHeight(frame,imageList) .. ']]' ..
'{{calculator|type=hidden|id=x' .. (imgColNumb-1) .. 'y' .. (imgRowNumb-1) .. '|formula=' .. formula .. '|default=' .. zDefault .. '}}'
) )
end
end
return tostring(html)
end
function p.makeViewer(frame)
local pFrame = frame:getParent()
local args = pFrame.args
local width = args['width'] or 250
assert( args['images'], "Images argument required")
local imageList = parseImageList( args['images'] )
local html = mw.html.create( 'div' )
html:addClass( 'spatialviewer calculator-container' )
html:cssText( args['style'] )
html:css( 'float', args['float'] )
:css( 'display', 'grid' )
:css( 'width', 'max-content' )
:css( 'grid-template-rows', '1fr ' .. width .. 'px 1fr' )
:css( 'border', '1px var(--border-color-base,#a2a9b1) solid' )
:css( 'padding', '0.5em' )
:css( 'gap', '3px' )
:css( 'height', getHeight(pFrame, imageList))
:css( 'background', args['background'] or '#fff' ) -- Should this be var(--background-color-base, '#fff') ?
html:tag( 'div' )
:css( 'display', 'none' ) -- Only display if calculator gadget enabled
:addClass( 'calculatorgadget-enabled' )
:css( 'grid-column', '1/4' )
:css( 'margin', 'auto' )
:wikitext(
getButton( pFrame, imageList, 'up' )
)
html:tag( 'div' )
:css( 'display', 'none' ) -- Only display if calculator gadget enabled
:addClass( 'calculatorgadget-enabled' )
:css( 'margin-top', 'auto' )
:css( 'margin-bottom', 'auto' )
:wikitext(
getButton( pFrame, imageList, 'left' )
)
html:tag( 'div' )
:wikitext(
getImages( pFrame, imageList )
)
html:tag( 'div' )
:css( 'display', 'none' ) -- Only display if calculator gadget enabled
:addClass( 'calculatorgadget-enabled' )
:css( 'margin-top', 'auto' )
:css( 'margin-bottom', 'auto' )
:wikitext(
getButton( pFrame, imageList, 'right' )
)
html:tag( 'div' )
:css( 'display', 'none' ) -- Only display if calculator gadget enabled
:addClass( 'calculatorgadget-enabled' )
:css( 'grid-column', '1/4' )
:css( 'margin', 'auto' )
:wikitext(
getButton( pFrame, imageList, 'down' )
)
html:wikitext(
frame:preprocess(
'{{calculator|id=x|type=hidden|default=' .. getStart('X',frame) .. '}}' ..
'{{calculator|id=y|type=hidden|default=' .. getStart('Y',frame) .. '}}'
)
)
return tostring(html)
end
return p