Module:Color

From EarthMC
Jump to navigation Jump to search

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

--NOTE: For Tooltip Colors, please use Module:UIText

local p = {}

local getArgs = require('Module:Arguments').getArgs
local string = require('Module:String')
local table = require('Module:Table')
local yesno = require('Module:Yesno')
local libU = require('Module:LibU')

local aliases = mw.loadData('Module:Color/Aliases')
local colorData = mw.loadData('Module:Color/Data')

-----------------------------------------------------------------------------
-- Category:Single Color templates
--
-- Changes text color to a fixed color
-- Aliases: p.makeColor, p.colorText, p.wrapColor, p.ct
-----------------------------------------------------------------------------
function p.colorTemplates(frame)
	local args = getArgs(frame)
	local text = args[1] or ''
	local color = args['color']
	return string.pcall(p._colorTemplates, color, text)
end
-- p._colorTemplates(color : string/number/table, text : string/number/table)
function p._colorTemplates(color, text)
	if type(color) == 'table' and #color >= 3 then
		color = table.concat{ #color == 3 and 'rgb(' or 'rgba(', table.concat(color, ', ') , ')' }
	end
	
	local colorID = string.gsub(aliases[string.lower(color)] or string.lower(color), ' ', '_');
	local lightcolor = not not colorData.shadowColors[colorID]
	local colorClass = colorData.colorClasses[colorID]
	local colorHex = colorData.hexColors[colorID] or color
	
	local appliedClass = (lightcolor and 'light-color ' or '') .. (colorClass or '')
	local appliedStyle = colorClass and '' or 'color:' .. tostring(colorHex)
	return string.wrapHtml(type(text) == 'table' and table.concat(text, text.seperator or text.sep or text.s or '') or text, 'span', {
		class = appliedClass ~= '' and appliedClass or nil,
		style = appliedStyle ~= '' and appliedStyle or nil,
	})
end
p.makeColor = p._colorTemplates
p.colorText = p._colorTemplates
p.wrapColor = p._colorTemplates
p.ct = p._colorTemplates

---------------------------------------------------------------------------------
-- p.getColor(color?: string | table<number>) 
---------------------------------------------------------------------------------
function p.getColor(color)
	if type(color) == 'table' and #color >= 3 then
		color = table.concat{ #color == 3 and 'rgb(' or 'rgba(', table.concat(color, ', ') , ')' }
	end
	return colorData.hexColors[string.gsub(aliases[string.lower(color)] or string.lower(color), ' ', '_')] or nil
end

-----------------------------------------------------------------------------
-- Template:Color Conversion
--
-- Converts input between different coding techniques
-- Can also be used in modules
-----------------------------------------------------------------------------
function p.color_conversion(frame)
	local args = getArgs(frame)
	local output_type = args[1]
	local color = args[2]
	return mw.getCurrentFrame():preprocess(string.pcall(p._color_conversion, output_type, color, true))
end

function p._color_conversion(output_type, color, on_page)
	--Local variables
	local color_output = ''
	local color_digit = ''
	-- Categorization based on output_type
	-- Hex
	if string.lower(output_type) == 'hex' then
		local letters = { [10] = 'A', [11] = 'B', [12] = 'C', [13] = 'D', [14] = 'E', [15] = 'F' }
		-- Delete everything except numbers and commas
		color = string.gsub(color, 'rgba?%((.*)%)', '%1')
		-- Split the numbers apart
		color = mw.text.split( color, '%s*,%s*')
		-- Check if input is valid
		if tonumber(color[1]) < 0 or tonumber(color[1]) > 255 or tonumber(color[2]) < 0 or tonumber(color[2]) > 255 or tonumber(color[3]) < 0 or tonumber(color[3]) > 255 then
			color_output = string.error('color was invalid \"rgb(%q, %q, %q)', color[1], color[2], color[3])
		else
			for i = 3, 1, -1 do
				-- Repeat this for all 3 numbers
				for j = 1, 2, 1 do
					-- Repeat this 2 times for every number
					color_digit = color[i] % 16
					color_digit = letters[color_digit] or color_digit
					color[i] = math.floor(color[i] / 16)
					color_output = color_digit .. color_output
				end
			end
			-- Add # to the front
			if on_page then
				color_output = mw.text.nowiki('#') .. color_output
			else color_output = '#' .. color_output
			end
		end
	-- RGB
	elseif string.match(string.lower(output_type), 'rgba?') then
		local letters = { ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15 }
		-- Set color_output to base value
		if string.lower(output_type) == 'rgba' then
			color_output = 'rgba('
		else
			color_output = 'rgb('
		end
		-- Convert all letters to uppercase
		color = string.upper(color)
		-- Remove # character
		color = string.gsub(color, '#', '')
		-- Check if input is valid
		if string.match(color, '%x') then
			if #color == 3 then
				for i = 1, 3, 1 do
					local color_help = {}
					color_help[i] = string.sub(color, i, i)
					color_help[i] = letters[color_help[i]] or color_help[i]
					color_output = color_output .. color_help[i] + color_help[i] * 16
					if i ~= 3 then color_output = color_output .. ',' end
				end
			else
				local color_storage
				for i = 1, 6, 1 do
					local color_help = {}
					color_help[i] = string.sub(color, i, i)
					color_help[i] = letters[color_help[i]] or color_help[i]
					if i % 2 == 1 then
						color_storage = color_help[i] * 16
					end
					if i % 2 == 0 then
						color_storage = color_storage + color_help[i]
						color_output = color_output .. color_storage
					end
					if i == 3 or i == 5 then
						color_output = color_output .. ','
					end
				end
			end
			-- Close the bracket
			if string.lower(output_type) == 'rgba' then
				color_output = color_output .. ',1)'
			else
				color_output = color_output .. ')'
			end
		else 
			color_output = '<strong class="error">Template Error: color code was invalid \"' .. color .. '\"</strong>[[Category:Pages with template errors]]'
		end
	-- Int
	elseif string.match(string.lower(output_type), 'int') then
		local letters = { ['A'] = 10, ['B'] = 11, ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15 }
		if color:lower():match('rgb?') then
			-- Delete everything except numbers and commas
			color = string.gsub(color, 'rgba?%((.*)%)', '%1')
			-- Split the numbers apart
			color = mw.text.split(color, '%s*,%s*')
		
			-- Check if input is valid
			if tonumber(color[1]) < 0 or tonumber(color[1]) > 255 or tonumber(color[2]) < 0 or tonumber(color[2]) > 255 or tonumber(color[3]) < 0 or tonumber(color[3]) > 255 then
				color_output = '<strong class="error">Template Error: Invalid RGB color \"rgb(' .. color[1] .. ',' .. color[2] .. ',' .. color[3] .. ')\"</strong>[[Category:Pages with template errors]]'
			else
				color_output = tonumber(color[1]) * 65536 + tonumber(color[2]) * 256 + tonumber(color[3])
			end
		else
			-- Convert all letters to uppercase
			color = string.upper(color)
			if string.match(color, '(#)(%x)') then
				-- Remove # character
				color = string.gsub(color, '#', '')
				if #color == 3 then
					local color_help = {}
					for i = 1, 3, 1 do
						color_help[i] = string.sub(color, i, i)
						color_help[i] = letters[color_help[i]] or color_help[i]
					end
					color_output = color_help[1] * 65536 * 17 + color_help[2] * 256 * 17 + color_help[3] * 17
				else
					local color_help = {}
					for i = 1, 6, 1 do
						color_help[i] = string.sub(color, i, i)
						color_help[i] = letters[color_help[i]] or color_help[i]
						if i % 2 == 1 then
							color_help[i] = color_help[i] * 16
						end
					end
					color_output = (color_help[1] + color_help[2]) * 65536 + (color_help[3] + color_help[4]) * 256 + (color_help[5] + color_help[6])
				end
			else
				color_output = color
			end
		end
	else -- arg[1] is invalid
		color_output = string.error('Invalid Color Output %q', output_type)
	end
	return color_output
end

-----------------------------------------------------------------------------
-- Template:Color
--
-- Changes text color
-----------------------------------------------------------------------------
-- p.color(color : string/number/table, text : string/number/table)
function p.color(frame)
	local args = getArgs(frame)
	local text = args[2] or ''
	local color = args['color'] or args['c'] or args[1]
	return string.pcall(p._colorTemplates, color, text)
end
function p._color(color, text)
	return string.wrapHtml(text, '<font>', { style = { color = color } })
end

-----------------------------------------------------------------------------
-- Template:Color Display
--
-- Displays input color as HEX code and a small preview
-----------------------------------------------------------------------------
function p.colorDisplay(frame)
	local args = getArgs(frame)
	local color = args[1]
	local preserveCase = yesno(args['preserve_case'] or args['preservecase'] or args['case'] or args['pc'] or args['c'])
	return p._colorDisplay(color, preserveCase)
end
function p._colorDisplay(color, preserveCase)
	if color:find('rgb') then
		color = p._color_conversion('hex', color)
	end
	-- Remove all "#"
	if color:find('#') then
		color = color:gsub('#', '')
	end
	if color:match('^%x%x%x%x?$') or color:match('^%x%x%x%x%x%x$') then -- allows 3, 4, or 6 hex decimals
		color = '#' .. color
	end
	if not preserveCase then color = color:upper() end
	return string.wrapHtml('', 'span', {
		style = { ['background-color'] = color },
		class = 'color-display-block'
	}) .. libU.pipeline(
		table.concat{ '&nbsp;' .. color }, 'code', string.wrapTag
	)
end
p.color_display = p.colorDisplay -- deprecated; may still have usages outside this code
p._color_display = p._colorDisplay -- deprecated; may still have usages outside this code

-----------------------------------------------------------------------------
-- Template:Armor Colors
--
-- Creates an invisible table with armor colors. Used by {{Infobox armor}}
-----------------------------------------------------------------------------
function p.armorColors(frame)
	local function makeCell(htmlObj, piece, text)
		if text then
			htmlObj:tag('tr')
				:tag('td'):css{ style = 'align:right;' }
					:tag('b'):wikitext(piece):done()
				:done()
				:tag('td')
					:wikitext(p._colorDisplay(text))
				:done()
			:done()
		end
	end
	
	local args = getArgs(frame)
	local inputs = string.lower(args[1])
	local all_the_same = yesno(args['all'])
	
	-- Store preformated strings
	local preformated = {
		['fairy'] = string.wrapTag(table.map({
				{'A', '<font>', { style = { color = '#FF66B2' }}},
				{'N', '<font>', { style = { color = '#FF3399' }}},
				{'I', '<font>', { style = { color = '#FF33FF' }}},
				{'M', '<font>', { style = { color = '#FF66FF' }}},
				{'A', '<font>', { style = { color = '#9933FF' }}},
				{'T', '<font>', { style = { color = '#FF99FF' }}},
				{'E', '<font>', { style = { color = '#FF66B2' }}},
				{'D', '<font>', { style = { color = '#FF99CC' }}},
			}, string.wrapHtml), 'b'),
		['crystal'] = string.wrapTag(table.map({
				{ 'C', '<font>', { style = { color = '#FCF3FF' }}},
				{ 'H', '<font>', { style = { color = '#E5D1ED' }}},
				{ 'A', '<font>', { style = { color = '#C9A3D4' }}},
				{ 'N', '<font>', { style = { color = '#A875BD' }}},
				{ 'G', '<font>', { style = { color = '#8E51A6' }}},
				{ 'I', '<font>', { style = { color = '#6A2C82' }}},
				{ 'N', '<font>', { style = { color = '#5D1C78' }}},
				{ 'G', '<font>', { style = { color = '#46085E' }}}
			}, string.wrapHtml), 'b'),
	}
	
	if preformated[inputs] then 
		return preformated[inputs]
	else
		inputs = mw.text.split(inputs, ',')
		if all_the_same then
			return p._colorDisplay(inputs[1])
		else
			local ret = mw.html.create('table')
			local t = { 'Helmet:', 'Chestplate:', 'Leggings:', 'Boots:' }
			for i, v in ipairs(t) do
				makeCell(ret, v, inputs[i])
			end
			return tostring(ret:done())
		end
	end
end

----------------------------------------------------------------------------------------------------
-- Template:Note Sequence
--
-- Makes colored note sequence. Used for Harp songs.
----------------------------------------------------------------------------------------------------
function p.noteSequence(frame)
	local args = getArgs(frame)
	local sequence = args[1]
	local colors = { '#ff78e8', '#ffff00', '#00ff00', '#008000', '#9500a8', '#5900ff', '#00b3ff' }
	-- stores notation because they mess up positions in the editor
	local clefs = { '𝄞', '𝄢', '𝄡' }
	local mus = { bracket = '𝄕', barline = '𝄀', final = '𝄂', clef = clefs[math.random(3)] }
	
	local str =table.concat(table.map(args, function(sequence, count)
		local foo = {}
		for i = 1, #sequence, 1 do
			local num = tonumber(sequence:sub(i, i))
			if num and num >= 1 and num <= 7 then
				table.insert(foo, string.wrapHtml(p._color(colors[num], sequence:sub(i, i)), '<code>', { style = 'font-weight: bold;' }) .. '&thinsp;')
			elseif sequence:sub(i, i) == ' ' or sequence:sub(i, i) == '.' then
				table.insert(foo, p._color('gray', '&bull;') .. '&thinsp;')
			elseif sequence:sub(i, i) == '$' then
				if count ~= table.length(args) then
					table.insert(foo, mus.barline .. '&thinsp;<br>' .. mus.bracket .. mus.clef .. '&thinsp;')
				end -- else don't insert anything
			else
				table.insert(foo, sequence:sub(i, i))
			end
		end
		if sequence:sub(#sequence, #sequence) ~= '$' then
			table.insert(foo, '&thinsp;' .. mus.barline .. '&thinsp;')
		end
		return table.concat(foo)
	end))
	
	return string.wrapHtml((mus.bracket .. mus.clef .. '&thinsp;%s&thinsp;' .. mus.final
		):format(str), 'span', { style = {
			['background'] = 'rgba(0, 0, 0, 0.35)';
			['padding'] = '1.5px';
			['border-radius'] = '3px';
		}}
	)
end

--Finish Module
return p