Module:Sprite: Difference between revisions

From Modded Wiki
Jump to navigation Jump to search
mw.html seems to use a lot of memory. Converting elements to wikitext string where possible before inserting them into an existing HtmlBuilder significantly reduces that memory usage for some reason, even though that just happens later anyway on output.
m 91 revisions imported
 
(38 intermediate revisions by 6 users not shown)
Line 7: Line 7:
f = mw.getCurrentFrame()
f = mw.getCurrentFrame()
end
end
local data = args.data and mw.loadData( 'Module:' .. args.data ) or {}
local settings = data.settings
-- Default settings
-- Default settings
Line 18: Line 21:
local defaultStyle = default
local defaultStyle = default
if args.settings then
if settings then
local settings = mw.loadData( 'Module:' .. args.settings )
if not settings.stylesheet then
if not settings.stylesheet then
-- Make a separate clone of the current default settings
-- Make a separate clone of the current default settings
Line 34: Line 36:
local sprite = mw.html.create( 'span' ):addClass( 'sprite' )
local sprite = mw.html.create( 'span' ):addClass( 'sprite' )
sprite:tag( 'br' )
if setting( 'stylesheet' ) then
-- mw.html's css method performs very slow escaping, which doubles the time it takes
sprite:addClass(
-- to run, so we'll construct the styles manually, and put them in the cssText
setting( 'classname' ) or
-- method, which only does html escaping (which isn't slow)
mw.ustring.lower( setting( 'name' ):gsub( ' ', '-' ) ) .. '-sprite'
local styles = {}
)
else
-- for tint
sprite:css(
local classname = setting( 'classname' ) or mw.ustring.lower( setting( 'name' ):gsub( ' ', '-' ) ) .. '-sprite'
'background-image',
local css_image = "background"
'{{FileUrl|' .. ( setting( 'image' ) or setting( 'name' ) .. 'Sprite.png' ) .. '}}'
if setting( 'formask' ) then
)
classname = classname .. '-mask'
css_image = "mask"
end
end
if setting( 'class' ) then
sprite:addClass( setting( 'class' ) )
sprite:addClass( classname )
local class = setting( 'class' )
if class then
sprite:addClass( class )
end
end
local size = setting( 'size' )
local width = setting( 'width' ) or setting( 'size' )
local pos = math.abs( setting( 'pos' ) ) - 1
local height = setting( 'height' ) or setting( 'size' )
local tiles = setting( 'sheetsize' ) / size
local sheetWidth = setting( 'sheetsize' )
local left = pos % tiles * size
local tiles = sheetWidth / width
local top = math.floor( pos / tiles ) * size
local pos = setting( 'pos' ) - 1
local scale = setting( 'scale' )
local scale = setting( 'scale' )
if left > 0 or top > 0 then
local autoScale = setting( 'autoscale' )
sprite:css( 'background-position', '-' .. left * scale .. 'px -' .. top * scale .. 'px' )
if pos then
local left = pos % tiles * width * scale
local top = math.floor( pos / tiles ) * height * scale
if css_image == 'mask' then
styles[#styles + 1] = '-webkit-mask-position:-' .. left .. 'px -' .. top .. 'px'
end
styles[#styles + 1] = css_image .. '-position:-' .. left .. 'px -' .. top .. 'px'
end
end
if not setting( 'autoscale' ) and scale ~= defaultStyle.scale then
sprite:css( 'background-size', setting( 'sheetsize' ) * scale .. 'px auto' )
if not autoScale and scale ~= defaultStyle.scale then
if css_image == 'mask' then
styles[#styles + 1] = '-webkit-mask-size:' .. sheetWidth * scale .. 'px auto'
end
styles[#styles + 1] = css_image .. '-size:' .. sheetWidth * scale .. 'px auto'
end
end
if size ~= defaultStyle.size or ( not setting( 'autoscale' ) and scale ~= defaultStyle.scale ) then
if height ~= defaultStyle.size or width ~= defaultStyle.size or ( not autoScale and scale ~= defaultStyle.scale ) then
sprite:css( 'height', size * scale .. 'px;width:' .. size * scale .. 'px' )
styles[#styles + 1] = 'height:' .. height * scale .. 'px'
styles[#styles + 1] = 'width:' .. width * scale .. 'px'
end
end
if setting( 'align' ) ~= defaultStyle.align then
sprite:css( 'vertical-align', setting( 'align' ) )
local align = setting( 'align' )
end
if align ~= defaultStyle.align then
if css then
styles[#styles + 1] = 'vertical-align:' .. align
sprite:cssText( css )
end
end
styles[#styles + 1] = setting( 'css' )
sprite:cssText( table.concat( styles, ';' ) )
local text = setting( 'text' )
local root
local root
local spriteText
local spriteText
if setting( 'text' ) then
if text then
root = mw.html.create( 'span' ):addClass( 'nowrap' )
if not args['wrap'] then
spriteText = mw.html.create( 'span' ):addClass( 'sprite-text' ):wikitext( setting( 'text' ) )
root = mw.html.create( 'span' ):addClass( 'nowrap' )
end
spriteText = mw.html.create( 'span' ):addClass( 'sprite-text' ):wikitext( text )
end
end
if setting( 'title' ) then
local title = setting( 'title' )
( root or sprite ):attr( 'title', setting( 'title' ) )
if title then
( root or sprite ):attr( 'title', title )
end
end
Line 92: Line 115:
end
end
local link = setting( 'link' )
local link = setting( 'link' ) or ''
if link and mw.ustring.lower( link ) ~= 'none' then
if link ~= '' and mw.ustring.lower( link ) ~= 'none' then
-- External link
-- External link
if link:find( '//' ) then
if link:find( '//' ) then
Line 111: Line 134:
if f == mw.getCurrentFrame() then
if f == mw.getCurrentFrame() then
args = require( 'Module:ProcessArgs' ).merge( true )
args = require( 'Module:ProcessArgs' ).merge( true )
else
f = mw.getCurrentFrame()
end
end
local data = args.data and mw.loadData( 'Module:' .. args.data ) or {}
local categories = {}
local categories = {}
if tonumber( args[1] ) then
local idData = args.iddata
args.pos = args[1]
if not idData then
table.insert( categories, '[[Category:Pages using sprite positions]]' )
local name = args.name or data.settings.name
else
local id = mw.text.trim( tostring( args[1] or '' ) )
local idData = args.iddata
idData = data.ids[id] or data.ids[mw.ustring.lower( id ):gsub( '[%s%+]', '-' )]
if not idData then
end
local default = {}
if args.settings then
local title = mw.title.getCurrentTitle()
default = mw.loadData( 'Module:' .. args.settings )
-- Remove categories on language pages, talk pages, and in User/UserWiki/UserProfile namespaces
local disallowCats = args.nocat or title.isTalkPage or title.nsText:find( '^User' )
if idData then
if idData.deprecated then
args.class = ( args.class or '' ) .. ' sprite-deprecated'
if not disallowCats then
categories[#categories + 1] = f:expandTemplate{ title = 'Translation category', args = { 'Pages using deprecated sprite names', project = 0 } }
end
end
local name = args.name or default.name
local ids = mw.loadData( 'Module:' .. ( args.ids or default.ids or name .. '/IDs' ) ).ids
local id = mw.text.trim( args[1] or '' )
idData = ids[id] or ids[mw.ustring.lower( id ):gsub( '[%s%+]', '-' )]
end
end
local allowCats = not mw.title.getCurrentTitle().isSubpage
args.pos = idData.pos
if not idData and allowCats then
elseif not disallowCats then
table.insert( categories, '[[Category:Pages with missing sprites]]' )
categories[#categories + 1] = f:expandTemplate{ title = 'Translation category', args = { 'Pages with missing sprites', project = 0 } }
else
if idData.deprecated and allowCats then
table.insert( categories, '[[Category:Pages using deprecated sprite names]]' )
end
args.pos = idData.pos
end
end
end
return p.base( args ), table.concat( categories, '' )
return p.base( args ), table.concat( categories )
end
end


Line 156: Line 176:
link = args[1]:match( '^(.-)%+' ) or args[1]
link = args[1]:match( '^(.-)%+' ) or args[1]
end
end
local text = args.text or args[2] or link
local text
if not args.notext then
text = args.text or args[2] or link
end
args[1] = args.id or args[1]
args[1] = args.id or args[1]
Line 172: Line 195:
f = mw.getCurrentFrame()
f = mw.getCurrentFrame()
end
end
local settingsPage = mw.text.trim( args[1] )
local dataPage = mw.text.trim( args[1] )
local settings = mw.loadData( 'Module:' .. settingsPage )
local data = mw.loadData( 'Module:' .. dataPage )
local idsPage = 'Module:' .. ( settings.ids or settings.name .. '/IDs' )
local getProtection = function( title, action, extra )
local protections = { 'edit' }
if extra then
protections[#protections + 1] = extra
end
local addProtection = function( protection )
if protection == 'autoconfirmed' then
protection = 'editsemiprotected'
elseif protection == 'sysop' then
protection = 'editprotected'
end
protections[#protections + 1] = protection
end
local direct = title.protectionLevels[action] or {}
for _, protection in ipairs( direct ) do
addProtection( protection )
end
local cascading = title.cascadingProtection.restrictions[action] or {}
if #cascading > 0 then
protections[#protections + 1] = 'protect'
end
for _, protection in ipairs( cascading ) do
addProtection( protection )
end
return table.concat( protections, ',' )
end
local body
local spriteStyle = ''
if args.refresh then
if data.settings.url and data.settings.url.style then
body = mw.html.create()
spriteStyle = data.settings.url.style
else
local spriteSheet = settings.image or settings.name .. 'Sprite.png'
body = mw.html.create( 'div' ):attr( {
id = 'spritedoc',
['data-idspage'] = '{{PAGEID:' .. idsPage .. '}}',
['data-idstimestamp'] = '{{REVISIONTIMESTAMP:' .. idsPage .. '}}',
['data-spritesheet'] = spriteSheet,
['data-pos'] = settings.pos or 1,
['data-refreshtext'] = mw.text.nowiki( '{{#invoke:sprite2|doc|' .. settingsPage .. '|refresh=1}}' )
} )
end
end
local data = mw.loadData( idsPage )
local dataTitle = mw.title.new( 'Module:' .. dataPage )
-- Temporary until this is updated
local spritesheet = data.settings.image or data.settings.name .. 'Sprite.png'
local spriteTitle = mw.title.new( 'File:' .. spritesheet )
local dataProtection = getProtection( dataTitle, 'edit' )
local spriteProtection = getProtection( spriteTitle, 'upload', 'upload,reupload' )
local body = mw.html.create( 'div' ):attr( {
id = 'spritedoc',
['data-dataprotection'] = dataProtection,
['data-datatimestamp'] = f:callParserFunction( 'REVISIONTIMESTAMP', 'Module:' .. dataPage ),
['data-datapage'] = 'Module:' .. dataPage,
['data-spritesheet'] = spritesheet,
['data-spriteprotection'] = spriteProtection,
['data-refreshtext'] = mw.text.nowiki( '{{#invoke:sprite|doc|' .. dataPage .. '|refresh=1}}' ),
['data-settings'] = mw.text.jsonEncode( data.settings ),
} )
local sections = {}
local sections = {}
for _, sectionData in ipairs( data.sections or { 'Uncategorized' } ) do
for _, sectionData in ipairs( data.sections or { name = 'Uncategorized' } ) do
local sectionTag = body:tag( 'div' ):addClass( 'spritedoc-section' ):attr( 'data-section-id', sectionData.id )
local sectionTag = body:tag( 'div' ):addClass( 'spritedoc-section' ):attr( 'data-section-id', sectionData.id )
-- https://phabricator.wikimedia.org/T73594
sectionTag:tag( 'h3' ):wikitext( sectionData.name )
sectionTag:wikitext( '<h3>', sectionData[1], '</h3>' )
sections[sectionData.id] = { boxes = sectionTag:tag( 'ul' ):addClass( 'spritedoc-boxes' ) }
sections[sectionData.id] = { boxes = sectionTag:tag( 'ul' ):addClass( 'spritedoc-boxes' ) }
end
end
local keyedData = {}
local keyedData = {}
local i = 1
for name, idData in pairs( data.ids ) do
for name, idData in pairs( data.ids ) do
table.insert( keyedData, {
keyedData[i] = {
sortKey = mw.ustring.lower( name ),
sortKey = mw.ustring.lower( name ),
name = name,
name = name,
data = idData
data = idData
} )
}
i = i + 1
end
end
table.sort( keyedData, function( a, b )
table.sort( keyedData, function( a, b )
Line 221: Line 280:
local box = section.boxes:tag( 'li' ):addClass( 'spritedoc-box' ):attr( 'data-pos', pos )
local box = section.boxes:tag( 'li' ):addClass( 'spritedoc-box' ):attr( 'data-pos', pos )
box:tag( 'div' ):addClass( 'spritedoc-image' )
box:tag( 'div' ):addClass( 'spritedoc-image' )
:wikitext( p.base{ pos = pos, settings = settingsPage } )
:wikitext( p.base{ pos = pos, data = dataPage, nourl = spriteStyle ~= '' } )
names = box:tag( 'ul' ):addClass( 'spritedoc-names' )
names = box:tag( 'ul' ):addClass( 'spritedoc-names' )
Line 236: Line 295:
if args.refresh then
if args.refresh then
return tostring( body )
return '', '', tostring( body )
end
end
return f:callParserFunction( '#widget:Stylesheet', { page = 'SpriteDoc' } ), tostring( body )
local styles = f:extensionTag( 'templatestyles', '', { src = 'Sprite/doc.css' } )
return styles, spriteStyle, tostring( body )
end
end
return p
return p

Latest revision as of 12:26, 4 July 2024

Documentation for this module may be created at Module:Sprite/doc

local p = {}
function p.base( f )
	local args = f
	if f == mw.getCurrentFrame() then 
		args = require( 'Module:ProcessArgs' ).merge( true )
	else
		f = mw.getCurrentFrame()
	end
	
	local data = args.data and mw.loadData( 'Module:' .. args.data ) or {}
	local settings = data.settings
	
	-- Default settings
	local default = {
		scale = 1,
		sheetsize = 256,
		size = 16,
		pos = 1,
		align = 'text-top'
	}
	
	local defaultStyle = default
	if settings then
		if not settings.stylesheet then
			-- Make a separate clone of the current default settings
			defaultStyle = mw.clone( default )
		end
		for k, v in pairs( settings ) do
			default[k] = v
		end
	end
	
	local setting = function( arg )
		return args[arg] or default[arg]
	end
	
	local sprite = mw.html.create( 'span' ):addClass( 'sprite' )
	
	-- mw.html's css method performs very slow escaping, which doubles the time it takes
	-- to run, so we'll construct the styles manually, and put them in the cssText
	-- method, which only does html escaping (which isn't slow)
	local styles = {}
	
	-- for tint
	local classname = setting( 'classname' ) or mw.ustring.lower( setting( 'name' ):gsub( ' ', '-' ) ) .. '-sprite'
	local css_image = "background"
	if setting( 'formask' ) then
		classname = classname .. '-mask'
		css_image = "mask"
	end
	
	sprite:addClass( classname )
	local class = setting( 'class' )
	if class then
		sprite:addClass( class )
	end
	
	local width = setting( 'width' ) or setting( 'size' )
	local height = setting( 'height' ) or setting( 'size' )
	local sheetWidth = setting( 'sheetsize' )
	local tiles = sheetWidth / width
	local pos = setting( 'pos' ) - 1
	local scale = setting( 'scale' )
	local autoScale = setting( 'autoscale' )
	
	if pos then
		local left = pos % tiles * width * scale
		local top = math.floor( pos / tiles ) * height * scale
		if css_image == 'mask' then
			styles[#styles + 1] = '-webkit-mask-position:-' .. left .. 'px -' .. top .. 'px'
		end
		styles[#styles + 1] = css_image .. '-position:-' .. left .. 'px -' .. top .. 'px'
	end
	
	if not autoScale and scale ~= defaultStyle.scale then
		if css_image == 'mask' then
			styles[#styles + 1] = '-webkit-mask-size:' .. sheetWidth * scale .. 'px auto'
		end
		styles[#styles + 1] = css_image .. '-size:' .. sheetWidth * scale .. 'px auto'
	end
	if height ~= defaultStyle.size or width ~= defaultStyle.size or ( not autoScale and scale ~= defaultStyle.scale ) then
		styles[#styles + 1] = 'height:' .. height * scale .. 'px'
		styles[#styles + 1] = 'width:' .. width * scale .. 'px'
	end
	
	local align = setting( 'align' )
	if align ~= defaultStyle.align then
		styles[#styles + 1] = 'vertical-align:' .. align
	end
	styles[#styles + 1] = setting( 'css' )
	
	sprite:cssText( table.concat( styles, ';' ) )
	
	local text = setting( 'text' )
	local root
	local spriteText
	if text then
		if not args['wrap'] then
			root = mw.html.create( 'span' ):addClass( 'nowrap' )
		end
		spriteText = mw.html.create( 'span' ):addClass( 'sprite-text' ):wikitext( text )
	end
	
	local title = setting( 'title' )
	if title then
		( root or sprite ):attr( 'title', title )
	end
	
	if not root then
		root = mw.html.create( '' )
	end
	root:node( sprite )
	if spriteText then
		root:node( spriteText )
	end
	
	local link = setting( 'link' ) or ''
	if link ~= '' and mw.ustring.lower( link ) ~= 'none' then
		-- External link
		if link:find( '//' ) then
			return '[' .. link .. ' ' .. tostring( root ) .. ']'
		end
		
		-- Internal link
		local linkPrefix = setting( 'linkprefix' ) or ''
		return '[[' .. linkPrefix .. link .. '|' .. tostring( root ) .. ']]'
	end
	
	return tostring( root )
end

function p.sprite( f )
	local args = f
	if f == mw.getCurrentFrame() then
		args = require( 'Module:ProcessArgs' ).merge( true )
	else
		f = mw.getCurrentFrame()
	end
	
	local data = args.data and mw.loadData( 'Module:' .. args.data ) or {}
	local categories = {}
	local idData = args.iddata
	if not idData then
		local name = args.name or data.settings.name
		local id = mw.text.trim( tostring( args[1] or '' ) )
		idData = data.ids[id] or data.ids[mw.ustring.lower( id ):gsub( '[%s%+]', '-' )]
	end
	
	local title = mw.title.getCurrentTitle()
	-- Remove categories on language pages, talk pages, and in User/UserWiki/UserProfile namespaces
	local disallowCats = args.nocat or title.isTalkPage or title.nsText:find( '^User' )
	if idData then
		if idData.deprecated then
			args.class = ( args.class or '' ) .. ' sprite-deprecated'
			if not disallowCats then
				categories[#categories + 1] = f:expandTemplate{ title = 'Translation category', args = { 'Pages using deprecated sprite names', project = 0 } }
			end
		end
		
		args.pos = idData.pos
	elseif not disallowCats then
		categories[#categories + 1] = f:expandTemplate{ title = 'Translation category', args = { 'Pages with missing sprites', project = 0 } }
	end
	
	return p.base( args ), table.concat( categories )
end

function p.link( f )
	local args = f
	if f == mw.getCurrentFrame() then
		args = require( 'Module:ProcessArgs' ).merge( true )
	end
	
	local link = args[1]
	if args[1] and not args.id then
		link = args[1]:match( '^(.-)%+' ) or args[1]
	end
	local text
	if not args.notext then
		text = args.text or args[2] or link
	end
	
	args[1] = args.id or args[1]
	args.link = args.link or link
	args.text = text
	
	return p.sprite( args )
end

function p.doc( f )
	local args = f
	if f == mw.getCurrentFrame() then
		args = f.args
	else
		f = mw.getCurrentFrame()
	end
	local dataPage = mw.text.trim( args[1] )
	local data = mw.loadData( 'Module:' .. dataPage )
	
	local getProtection = function( title, action, extra )
		local protections = { 'edit' }
		if extra then
			protections[#protections + 1] = extra
		end
		
		local addProtection = function( protection )
			if protection == 'autoconfirmed' then
				protection = 'editsemiprotected'
			elseif protection == 'sysop' then
				protection = 'editprotected'
			end
			
			protections[#protections + 1] = protection
		end
		
		local direct = title.protectionLevels[action] or {}
		for _, protection in ipairs( direct ) do
			addProtection( protection )
		end
		local cascading = title.cascadingProtection.restrictions[action] or {}
		if #cascading > 0 then
			protections[#protections + 1] = 'protect'
		end
		for _, protection in ipairs( cascading ) do
			addProtection( protection )
		end
		
		return table.concat( protections, ',' )
	end
	
	local spriteStyle = ''
	if data.settings.url and data.settings.url.style then
		spriteStyle = data.settings.url.style
	end
	
	local dataTitle = mw.title.new( 'Module:' .. dataPage )
	-- Temporary until this is updated
	local spritesheet = data.settings.image or data.settings.name .. 'Sprite.png'
	local spriteTitle = mw.title.new( 'File:' .. spritesheet )
	local dataProtection = getProtection( dataTitle, 'edit' )
	local spriteProtection = getProtection( spriteTitle, 'upload', 'upload,reupload' )
	local body = mw.html.create( 'div' ):attr( {
		id = 'spritedoc',
		['data-dataprotection'] = dataProtection,
		['data-datatimestamp'] = f:callParserFunction( 'REVISIONTIMESTAMP', 'Module:' .. dataPage ),
		['data-datapage'] = 'Module:' .. dataPage,
		['data-spritesheet'] = spritesheet,
		['data-spriteprotection'] = spriteProtection,
		['data-refreshtext'] = mw.text.nowiki( '{{#invoke:sprite|doc|' .. dataPage .. '|refresh=1}}' ),
		['data-settings'] = mw.text.jsonEncode( data.settings ),
	} )
	
	local sections = {}
	for _, sectionData in ipairs( data.sections or { name = 'Uncategorized' } ) do
		local sectionTag = body:tag( 'div' ):addClass( 'spritedoc-section' ):attr( 'data-section-id', sectionData.id )
		sectionTag:tag( 'h3' ):wikitext( sectionData.name )
		sections[sectionData.id] = { boxes = sectionTag:tag( 'ul' ):addClass( 'spritedoc-boxes' ) }
	end
	
	local keyedData = {}
	local i = 1
	for name, idData in pairs( data.ids ) do
		keyedData[i] = {
			sortKey = mw.ustring.lower( name ),
			name = name,
			data = idData
		}
		i = i + 1
	end
	table.sort( keyedData, function( a, b )
		return a.sortKey < b.sortKey
	end )
	
	for _, data in ipairs( keyedData ) do
		local idData = data.data
		local pos = idData.pos
		local section = sections[idData.section]
		local names = section[pos]
		if not names then
			local box = section.boxes:tag( 'li' ):addClass( 'spritedoc-box' ):attr( 'data-pos', pos )
			box:tag( 'div' ):addClass( 'spritedoc-image' )
				:wikitext( p.base{ pos = pos, data = dataPage, nourl = spriteStyle ~= '' } )
			
			names = box:tag( 'ul' ):addClass( 'spritedoc-names' )
			section[pos] = names
		end
		local nameElem = mw.html.create( 'li' ):addClass( 'spritedoc-name' )
		local codeElem = nameElem:tag( 'code' ):wikitext( data.name )
		
		if idData.deprecated then
			codeElem:addClass( 'spritedoc-deprecated' )
		end
		names:wikitext( tostring( nameElem ) )
	end
	
	if args.refresh then
		return '', '', tostring( body )
	end
	local styles = f:extensionTag( 'templatestyles', '', { src = 'Sprite/doc.css' } )
	return styles, spriteStyle, tostring( body )
end
return p