Minecraft Wiki
Регистрация
Advertisement

Этот модуль реализует систему спрайтов Minecraft Wiki. Больше информации расположено на указанной странице и на страницах документации шаблонов спрайтов.

Аргументы страницы, вызвавшей модуль, автоматически объединяются с аргументами, переданными модулю напрямую (последние перезаписывают первые). Также все аргументы нормализуются (при этом удаляются стоящие в начале и в конце пробелы, а пустые аргументы задаются как равные nil).

Зависит от

-- Модуль для отображения иконок из таблиц спрайтов.
-- Внимание: Неосторожные изменения модуля могут привести к проблемам на большом количестве статей!
local p = {}
local h = {}

-- Faster than mw.text.trim, but does not have the memory penalty of gsub-based
-- solutions
function h.fasterTrim(str)
	local byte = string.byte
	local startIndex = 0
	local space, newline, tab = byte(" \n\t", 1, 3)
	local strByte
	
	repeat
        startIndex = startIndex + 1
        strByte = byte(str, startIndex, startIndex)
	until (strByte ~= space) and (strByte ~= newline) and (strByte ~= tab)
    
    local endIndex = str:len() + 1
	repeat
        endIndex = endIndex - 1
        strByte = byte(str, endIndex, endIndex)
	until (strByte ~= space) and (strByte ~= newline) and (strByte ~= tab)
    
    return str:sub(startIndex, endIndex)
end
	
function h.getProtection(title, action, extra)
    local protections = {'edit', extra}

    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

-- Создание спрайта
function p.base(f)
	local args = f
	if f == mw.getCurrentFrame() then 
		args = require('Модуль:ProcessArgs').merge(true)
	end
	
	local data = args['данные'] and mw.loadData( 'Модуль:' .. args['данные'] ) or {}
	local settings = data['настройки']
	
	local default = {
		["масштаб"] = 1,
		["формат"] = 256,
		["разм"] = 16,
		["поз"] = 1,
		["выравн"] = 'text-top',
	}
	
	local defaultStyle = default
	if settings then
		if not settings['таблстилей'] then
			-- Создаём отдельную копию текущих настроек по умолчанию
			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')
	-- Метод css от mw.html производит очень медленное экранирование входных данных, что тормозит работу в два раза. Вместо
	-- этого стили будут создаваться вручную, и будут передаваться через метод cssText, который делает только экранирование HTML,
	-- что куда быстрее.
	local styles = {}

	local page = setting('страница') or setting('главная_страница')
	
	local urlSetting = setting( 'url' )
	if not setting( 'nourl' ) and urlSetting then
		styles[#styles + 1] = 'background-image:' .. (urlSetting.url or urlSetting)
	end
	if setting( 'таблстилей' ) then
		sprite:addClass(
			setting( 'имякласса' ) or
			mw.ustring.lower( setting( 'имя' ):gsub( ' ', '-' ) ) .. '-sprite'
		)
	elseif page then
		-- временная заглушка для GregTech
		styles[#styles + 1] = 'background-image: {{FileUrl|' .. (setting('изобр') or setting('имя') .. page .. 'CSS.png') .. '}}'
	elseif not urlSetting then
		styles[#styles + 1] = 'background-image:' .. p.getUrl(
			setting( 'изобр' ) or (setting( 'имя' ) .. 'CSS.png')
		).url
	end
	local class = setting( 'класс' )
	if class then
		sprite:addClass( class )
	end
	
	-- Настройки страницы многостраничного спрайта
	local scaleq = 1
	if setting(page) then
		args['масштаб'] = args['масштаб'] or 1
		scaleq = setting(page)['множитель'] or 1
		args['разм'] = setting(page)['разм'] or setting('разм')
		args['формат'] = setting(page)['формат'] or setting('формат')
	else
		scaleq = setting('множитель') or 1
	end
	
	local size = setting('разм') -- размер спрайта в пикселях
	local v_size = setting('верт_разм') or size -- размер спрайта в пикселях в высоту
	local pos = math.abs(setting('поз')) - 1 -- положение спрайта в таблице
	local sheetWidth = setting('формат') -- ширина таблицы спрайта в пикселях
	local tiles = sheetWidth / size -- количество спрайтов в одной строке
	local left = pos % tiles * size -- горизонтальная координата спрайта 
	local top = math.floor(pos / tiles ) * v_size -- вертикальная координата спрайта
	local scale = setting('масштаб') * scaleq -- масштаб спрайта (во сколько раз увеличить или уменьшить размер)
	local autoscale = setting('автомасштаб') -- автоматическое применение масштабирования
	local align = setting('выравн') -- выравнивание по вертикали
	
	-- Координаты
	if left > 0 or top > 0 then
		styles[#styles + 1] = 'background-position: -' .. left * scale .. 'px -' .. top * scale .. 'px'
	end

	-- Масштаб
	local nonDefaultScale = not autoscale and scale ~= defaultStyle["масштаб"]
	if nonDefaultScale then
		styles[#styles + 1] = 'background-size: ' .. sheetWidth * scale .. 'px auto'
	end
	
	-- Размеры спрайта
	if size ~= defaultStyle["разм"] or nonDefaultScale then
		styles[#styles + 1] = 'height: ' .. v_size * scale .. 'px'
		styles[#styles + 1] = 'width: ' .. size * scale .. 'px'
	end
	
	-- Выравнивание
	if align ~= defaultStyle["выравн"] then
		styles[#styles + 1] = 'vertical-align: ' .. align
	end
	
	-- Дополнительный CSS-код, указанный в параметре
	styles[#styles + 1] = setting('css')
	
	-- Применение полученных CSS-стилей к спрайту.
	sprite:cssText(table.concat(styles, ';'))
	
	-- Собственно спрайт.	
	local root
	-- Текстовые данные
	local text = setting('текст')
	local spriteText
	if text and (text ~= 'нет') then
		root = mw.html.create( 'span' ):addClass( 'nowrap' )
		spriteText = mw.html.create( 'span' ):addClass( 'sprite-text' ):wikitext( text )
	end
	
	-- Всплывающий текст
	local title = setting('назв')
	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( 'ссылка' ) or ''
	if link ~= '' and mw.ustring.lower( link ) ~= 'none' then
		-- Внешняя ссылка
		if link:find( '//' ) then
			return '[' .. link .. ' ' .. tostring( root ) .. ']'
		end
		
		-- Внутренняя ссылка. Поддерживается префикс, что полезно при ссылке на модификации.
		local linkPrefix = setting( 'предссылки' ) or ''
		return '[[' .. linkPrefix .. link .. '|' .. tostring( root ) .. ']]'
	end
	
	return tostring( root )
end

-- Данная функция предварительно готовит данные для функции p.base, а затем вызывает её. Её следует вызывать на страницах вики-проекта
-- (через любой из спрайтовых шаблонов)
function p.sprite(f)
	-- Параметры
	local args = f
	if f == mw.getCurrentFrame() then
		args = require('Модуль:ProcessArgs').merge(true)
	else
		f = mw.getCurrentFrame()
	end
	
	local data = args['данные'] and mw.loadData( 'Модуль:' .. args['данные'] ) or {}
	local categories = {}
	local idData = args["данныеID"] -- данные по названиям спрайтов и их позициям
	
	local idListName = data['настройки']['списокID']
	local idList = data['IDы']
	if idListName then
		idList = mw.loadData( 'Модуль:' .. idListName )['IDы']
	end
	
	if not idData then
		local name = args["имя"] or data["настройки"]["имя"]
		local id = h.fasterTrim( tostring( args[1] or '' ) )
		idData = idList[id] or idList[mw.ustring.lower(id):gsub('[_%-%s%+]+', '-')]
	end
	local title = mw.title.getCurrentTitle()
	
	-- запретить категории соответственно в подстраницах, на страницах обсуждений и в пространствах участников,
	-- а также если установлен параметр «некат»
	local disallowCats = args["некат"] or title.isSubpage or title.isTalkPage or title:inNamespace(2)
	if idData then
		if idData["устарел"] then
			args["класс"] = ( args["класс"] or '' ) .. ' sprite-deprecated'		
			if not disallowCats then
				categories[#categories + 1] = '[[Категория:Страницы с устаревшими названиями спрайтов]]'
			end
		end
		
		args["поз"] = idData["поз"]
		args["страница"] = idData["страница"]
	elseif not disallowCats then
		categories[#categories + 1] = '[[Категория:Страницы с отсутствующими спрайтами]]'
	end
	return p.base(args), table.concat(categories)
end

-- Ссылки
function p.link(f)
	local args = f
	if f == mw.getCurrentFrame() then
		args = require('Модуль: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["безтекста"] then
		text = args["текст"] or args[2] or link
	end
	
	args[1] = args["ID"] or args[1]
	args["ссылка"] = args["ссылка"] or link
	args["текст"] = text
	
	return p.sprite( args )
end

function p.getUrl( image, query, classname )
	local f = mw.getCurrentFrame()
	local t = {
		url = f:expandTemplate{
			title = 'FileUrl',
			args = { image, query = query }
		},
	}
	if classname and classname ~= '' then
		t.style = f:expandTemplate{
			title = 'FileUrlStyle',
			args = { classname, image, query = query }
		}
	end
	return t
end

function p.getParsedUrlStyle( f )
	local args = f:getParent().args
	local module = args[1]
	return require( 'Модуль:' .. module )["настройки"].url.style
end

-- Документация по таблице спрайтов. Показывает названия спрайтов в таблице и их категории.
function p.doc(f)
	-- Параметры
    local args = f
    if f == mw.getCurrentFrame() then
        args = f.args
    else
        f = mw.getCurrentFrame()
    end
	
	local dataPage = h.fasterTrim( args[1] )
	local data = mw.loadData( 'Модуль:' .. dataPage )
	
	local idDataSections = data['разделы']
	local idDataList = data['IDы']
	local idDataListOverride = data['настройки']['списокID']
	if idDataListOverride then
		local overrideList = mw.loadData( 'Модуль:' .. idDataListOverride )
		idDataList = overrideList['IDы']
		idDataSections = overrideList['разделы']
	end
	
	local spriteStyle = ''
	if data["настройки"].url and data["настройки"].url.style then
		spriteStyle = data["настройки"].url.style
	end
	
	local dataTitle = mw.title.new( 'Модуль:' .. dataPage )
	-- Temporary until this is updated
	local classname = ''
	if data["настройки"]["таблстилей"] then
		classname = data["настройки"]["имякласса"] or
			mw.ustring.lower( data["настройки"]["имя"]:gsub( ' ', '-' ) ) .. '-sprite'
	end
	local spritesheet = data["настройки"]["изобр"] or data["настройки"]["имя"] .. 'CSS.png'
	local spriteTitle = mw.title.new( 'Файл:' .. spritesheet )
	local dataProtection = h.getProtection( dataTitle, 'edit' )
	local spriteProtection = h.getProtection( spriteTitle, 'upload', 'upload,reupload' )
	local body = mw.html.create( 'div' ):attr( {
		id = 'spritedoc',
		['data-dataprotection'] = dataProtection,
		['data-datatimestamp'] = f:callParserFunction( 'REVISIONTIMESTAMP', 'Модуль:' .. dataPage ),
		['data-datapage'] = 'Модуль:' .. dataPage,
		['data-spritesheet'] = spritesheet,
		['data-spriteprotection'] = spriteProtection,
		['data-urlfunc'] = "require( [[Модуль:Спрайт]] ).getUrl( '" .. spritesheet .. "', '$1', '" .. classname .. "' )",
		['data-refreshtext'] = mw.text.nowiki( '{{#invoke:Спрайт|doc|' .. dataPage .. '|refresh=1}}' ),
		['data-settings'] = mw.text.jsonEncode( data["настройки"] ),
	} )
	
	local sections = {}
	for _, sectionData in ipairs( idDataSections or { ["назв"] = 'Некатегоризованные' } ) do
		local sectionTag = body:tag( 'div' ):addClass( 'spritedoc-section' ):attr( 'data-section-id', sectionData.ID )
		sectionTag:tag( 'h3' ):wikitext( sectionData["назв"] )
		sections[sectionData.ID] = { boxes = sectionTag:tag( 'ul' ):addClass( 'spritedoc-boxes' ) }
	end
	
	local keyedData = {}
	local i = 1
	for name, idData in pairs( idDataList ) 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 )

	local spriteArgs = { ["поз"] = -1, ["данные"] = dataPage, nourl = spriteStyle ~= '' }
	for _, data in ipairs( keyedData ) do
		local idData = data.data
		local pos = idData['поз']
		if not pos then
			error(string.format('Не найдено позиции у спрайта `%s`', data.name or "?"))
		end
		spriteArgs['поз'] = pos
		local section = sections[idData['раздел']]
		if not section then
			error(("У спрайта «%s» (позиция %d) указан некорректный ID раздела: %s"):format(data.name, pos, idData['раздел']))
		end
		spriteArgs['страница'] = idData['страница']
		
		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(spriteArgs) )
			
			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['устарел'] then
			codeElem:addClass( 'spritedoc-deprecated' )
		end
		names:wikitext( tostring( nameElem ) )
	end
	
	if args.refresh then
		return '', '', tostring( body )
	end
	local styles = f:callParserFunction( '#widget:SpriteDoc.css' )
	return styles, spriteStyle, tostring( body )
end

function p.sortedByIndex(f)
    local args = f
    if f == mw.getCurrentFrame() then
        args = f.args
    else
        f = mw.getCurrentFrame()
    end
	
	local dataPage = h.fasterTrim( args[1] )
	local data = mw.loadData( 'Модуль:' .. dataPage )
	
	local translatedData = {}
	local highestPos = 0
	for k, v in pairs(data['IDы']) do
		local pos = v['поз']
		if not translatedData[pos] then
			translatedData[pos] = {['раздел'] = v['раздел']} -- один спрайт не может иметь идентификаторы в разных разделах?
			if pos > highestPos then highestPos = pos; end
		end
		table.insert(translatedData[pos], {['назв'] = k, ['устарел'] = v['устарел']})
	end
	
	for i = 1, highestPos do
		if not translatedData[i] then
			translatedData[i] = {missing = true}
		end
	end
	
	local result = {'{| class="wikitable sortable collapsible collapsed"', "! Позиция !! Раздел !! Спрайт !! Идентификаторы"}
	for i, v in ipairs(translatedData) do
		if not v.missing then
			table.insert(result, "|-")
			table.insert(result, ("| %d"):format(i))
			table.insert(result, ("| %d"):format(v['раздел']))
			table.insert(result, "| " .. p.base({['данные'] = dataPage, ['поз'] = i}))
			table.insert(result, "|")
			for i2, v2 in ipairs(v) do -- ⚠️
				table.insert(result, ("* %s<code>%s</code>"):format(v2['устарел'] == true and "'''(устарел)''' " or "", v2['назв']))
			end
		end
	end
	table.insert(result, "|}")
	
	return table.concat(result, "\n")
end

return p
Advertisement