mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-23 12:07:27 +01:00
TGUI Color Picker (#26326)
* Init * Implements `tgui_input_color` * Implements most color wheel conversions to TGUI * Iteration two, went over titles and made sure most color inputs are functional. * Implements preferences for large buttons, swapped buttons, and disabled TGUI * Iteration three... the weird ones. (as null|color) * Removes `as color|null` and `as null|color` * Merge conflict resolution, again. * Color input bundle and panel bundle * CSLint being picky. * Picky `while ()` instead of `while()` * Apply suggestions from code review Co-authored-by: Aylong <69762909+AyIong@users.noreply.github.com> Signed-off-by: Spaghetti-bit <yumyumkillkill@gmail.com> * WIP * de-bri'ish colour into color Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com> Signed-off-by: Spaghetti-bit <yumyumkillkill@gmail.com> * Aylong review applied. - Removed an extra space in machine.dm - UI_STATE + Runechat color now uses TGUI_INPUT_COLOR * Apply suggestions from code review Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com> Signed-off-by: Spaghetti-bit <yumyumkillkill@gmail.com> * Removes an unintentional space to `paradise.scss` * Update code/modules/tgui/tgui_input/color_input.dm Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com> Signed-off-by: Spaghetti-bit <yumyumkillkill@gmail.com> * Adds "is_color" regex from TG * Fixes a few conflicting files * TGUI Merge conflict moment v3 * V4? * V5.... * God please don't explode. Builds TGUI like the linter wants. * Update code/modules/tgui/tgui_input/color_input.dm Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com> Signed-off-by: Spaghetti-bit <yumyumkillkill@gmail.com> * Rebuilds TGUI, again <3 * Lint be damned --------- Signed-off-by: Spaghetti-bit <yumyumkillkill@gmail.com> Co-authored-by: Aylong <69762909+AyIong@users.noreply.github.com> Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com> Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>
This commit is contained in:
co-authored by
Contrabang
Aylong
Burzah
parent
d0dc432024
commit
afd52f006a
@@ -1,2 +1,3 @@
|
||||
GLOBAL_DATUM_INIT(filename_forbidden_chars, /regex, regex(@{""|[\\\n\t/?%*:|<>]|\.\."}, "g"))
|
||||
GLOBAL_DATUM_INIT(is_color, /regex, regex("^#\[0-9a-fA-F]{6}$"))
|
||||
GLOBAL_PROTECT(filename_forbidden_chars)
|
||||
|
||||
@@ -818,9 +818,10 @@
|
||||
M.change_gender(FEMALE)
|
||||
|
||||
if(eyes_organ)
|
||||
var/new_eyes = input("Please select eye color.", "Character Generation", eyes_organ.eye_color) as null|color
|
||||
if(new_eyes)
|
||||
M.change_eye_color(new_eyes)
|
||||
var/new_eyes = tgui_input_color(user, "Please select eye color.", "Character Generation", eyes_organ.eye_color)
|
||||
if(isnull(new_eyes))
|
||||
return
|
||||
M.change_eye_color(new_eyes)
|
||||
|
||||
if(istype(head_organ))
|
||||
//Alt heads.
|
||||
@@ -838,14 +839,14 @@
|
||||
if(new_style)
|
||||
M.change_hair(new_style)
|
||||
|
||||
var/new_hair = input("Please select hair color.", "Character Generation", head_organ.hair_colour) as null|color
|
||||
if(new_hair)
|
||||
var/new_hair = tgui_input_color(user, "Please select hair color.", "Character Generation", head_organ.hair_colour)
|
||||
if(!isnull(new_hair))
|
||||
M.change_hair_color(new_hair)
|
||||
|
||||
var/datum/sprite_accessory/hair_style = GLOB.hair_styles_public_list[head_organ.h_style]
|
||||
if(hair_style.secondary_theme && !hair_style.no_sec_colour)
|
||||
new_hair = input("Please select secondary hair color.", "Character Generation", head_organ.sec_hair_colour) as null|color
|
||||
if(new_hair)
|
||||
new_hair = tgui_input_color(user, "Please select secondary hair color.", "Character Generation", head_organ.sec_hair_colour)
|
||||
if(!isnull(new_hair))
|
||||
M.change_hair_color(new_hair, TRUE)
|
||||
|
||||
// facial hair
|
||||
@@ -855,25 +856,25 @@
|
||||
if(new_style)
|
||||
M.change_facial_hair(new_style)
|
||||
|
||||
var/new_facial = input("Please select facial hair color.", "Character Generation", head_organ.facial_colour) as null|color
|
||||
if(new_facial)
|
||||
var/new_facial = tgui_input_color(user, "Please select facial hair color.", "Character Generation", head_organ.facial_colour)
|
||||
if(!isnull(new_facial))
|
||||
M.change_facial_hair_color(new_facial)
|
||||
|
||||
var/datum/sprite_accessory/facial_hair_style = GLOB.facial_hair_styles_list[head_organ.f_style]
|
||||
if(facial_hair_style.secondary_theme && !facial_hair_style.no_sec_colour)
|
||||
new_facial = input("Please select secondary facial hair color.", "Character Generation", head_organ.sec_facial_colour) as null|color
|
||||
if(new_facial)
|
||||
new_facial = tgui_input_color(user, "Please select secondary facial hair color.", "Character Generation", head_organ.sec_facial_colour)
|
||||
if(!isnull(new_facial))
|
||||
M.change_facial_hair_color(new_facial, TRUE)
|
||||
|
||||
//Head accessory.
|
||||
if(head_organ.dna.species.bodyflags & HAS_HEAD_ACCESSORY)
|
||||
var/list/valid_head_accessories = M.generate_valid_head_accessories()
|
||||
var/new_head_accessory = tgui_input_list(user, "Please select head accessory style", "Character Generation", valid_head_accessories)
|
||||
if(new_head_accessory)
|
||||
if(!isnull(new_head_accessory))
|
||||
M.change_head_accessory(new_head_accessory)
|
||||
|
||||
var/new_head_accessory_colour = input("Please select head accessory colour.", "Character Generation", head_organ.headacc_colour) as null|color
|
||||
if(new_head_accessory_colour)
|
||||
var/new_head_accessory_colour = tgui_input_color(user, "Please select head accessory color.", "Character Generation", head_organ.headacc_colour)
|
||||
if(!isnull(new_head_accessory_colour))
|
||||
M.change_head_accessory_color(new_head_accessory_colour)
|
||||
|
||||
|
||||
@@ -882,7 +883,7 @@
|
||||
var/list/valid_body_accessories = M.generate_valid_body_accessories()
|
||||
if(length(valid_body_accessories) > 1) //By default valid_body_accessories will always have at the very least a 'none' entry populating the list, even if the user's species is not present in any of the list items.
|
||||
var/new_body_accessory = tgui_input_list(user, "Please select body accessory style", "Character Generation", valid_body_accessories)
|
||||
if(new_body_accessory)
|
||||
if(!isnull(new_body_accessory))
|
||||
M.change_body_accessory(new_body_accessory)
|
||||
|
||||
if(istype(head_organ))
|
||||
@@ -890,32 +891,32 @@
|
||||
if(M.dna.species.bodyflags & HAS_HEAD_MARKINGS)
|
||||
var/list/valid_head_markings = M.generate_valid_markings("head")
|
||||
var/new_marking = tgui_input_list(user, "Please select head marking style", "Character Generation", valid_head_markings)
|
||||
if(new_marking)
|
||||
if(!isnull(new_marking))
|
||||
M.change_markings(new_marking, "head")
|
||||
|
||||
var/new_marking_colour = input("Please select head marking colour.", "Character Generation", M.m_colours["head"]) as null|color
|
||||
if(new_marking_colour)
|
||||
var/new_marking_colour = tgui_input_color(user, "Please select head marking color.", "Character Generation", M.m_colours["head"])
|
||||
if(!isnull(new_marking_colour))
|
||||
M.change_marking_color(new_marking_colour, "head")
|
||||
|
||||
//Body markings.
|
||||
if(M.dna.species.bodyflags & HAS_BODY_MARKINGS)
|
||||
var/list/valid_body_markings = M.generate_valid_markings("body")
|
||||
var/new_marking = tgui_input_list(user, "Please select body marking style", "Character Generation", valid_body_markings)
|
||||
if(new_marking)
|
||||
if(!isnull(new_marking))
|
||||
M.change_markings(new_marking, "body")
|
||||
|
||||
var/new_marking_colour = input("Please select body marking colour.", "Character Generation", M.m_colours["body"]) as null|color
|
||||
if(new_marking_colour)
|
||||
var/new_marking_colour = tgui_input_color(user, "Please select body marking color.", "Character Generation", M.m_colours["body"])
|
||||
if(!isnull(new_marking_colour))
|
||||
M.change_marking_color(new_marking_colour, "body")
|
||||
//Tail markings.
|
||||
if(M.dna.species.bodyflags & HAS_TAIL_MARKINGS)
|
||||
var/list/valid_tail_markings = M.generate_valid_markings("tail")
|
||||
var/new_marking = tgui_input_list("Please select tail marking style", "Character Generation", valid_tail_markings)
|
||||
if(new_marking)
|
||||
if(!isnull(new_marking))
|
||||
M.change_markings(new_marking, "tail")
|
||||
|
||||
var/new_marking_colour = input("Please select tail marking colour.", "Character Generation", M.m_colours["tail"]) as null|color
|
||||
if(new_marking_colour)
|
||||
var/new_marking_colour = tgui_input_color(user, "Please select tail marking color.", "Character Generation", M.m_colours["tail"])
|
||||
if(!isnull(new_marking_colour))
|
||||
M.change_marking_color(new_marking_colour, "tail")
|
||||
|
||||
//Skin tone.
|
||||
@@ -944,8 +945,8 @@
|
||||
|
||||
//Skin colour.
|
||||
if(M.dna.species.bodyflags & HAS_SKIN_COLOR)
|
||||
var/new_body_colour = input("Please select body colour.", "Character Generation", M.skin_colour) as null|color
|
||||
if(new_body_colour)
|
||||
var/new_body_colour = tgui_input_color(user, "Please select body color.", "Character Generation", M.skin_colour)
|
||||
if(!isnull(new_body_colour))
|
||||
M.change_skin_color(new_body_colour)
|
||||
|
||||
M.update_dna()
|
||||
|
||||
@@ -44,7 +44,9 @@
|
||||
src.add_fingerprint(user)
|
||||
if(stat & (BROKEN|NOPOWER))
|
||||
return
|
||||
var/temp = input(usr, "Choose a dye color", "Dye Color") as color
|
||||
var/temp = tgui_input_color(user, "Please select a dye color", "Dye Color")
|
||||
if(isnull(temp))
|
||||
return
|
||||
dye_color = temp
|
||||
set_light(2, l_color = temp)
|
||||
|
||||
|
||||
@@ -292,7 +292,9 @@
|
||||
if(!Adjacent(usr) || usr.incapacitated())
|
||||
return
|
||||
if(href_list["color"])
|
||||
var/temp = input(usr, "Please select colour.", "Crayon colour") as color
|
||||
var/temp = tgui_input_color(usr, "Please select crayon color.", "Crayon color")
|
||||
if(isnull(temp))
|
||||
return
|
||||
colour = temp
|
||||
update_window(usr)
|
||||
else
|
||||
@@ -328,7 +330,9 @@
|
||||
if("Change Drawing")
|
||||
..()
|
||||
if("Change Color")
|
||||
colour = input(user,"Choose Color") as color
|
||||
colour = tgui_input_color(user,"Please select a paint color.","Spray Can Color")
|
||||
if(isnull(colour))
|
||||
return
|
||||
update_icon()
|
||||
|
||||
/obj/item/toy/crayon/spraycan/afterattack(atom/target, mob/user as mob, proximity)
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
/obj/structure/curtain/attackby(obj/item/W, mob/user)
|
||||
if(istype(W, /obj/item/toy/crayon))
|
||||
color = input(user, "Choose Color") as color
|
||||
color = tgui_input_color(user,"Please choose a color.", "Curtain Color")
|
||||
return
|
||||
return ..()
|
||||
|
||||
|
||||
@@ -69,8 +69,8 @@
|
||||
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
|
||||
return
|
||||
if(!light_broken)
|
||||
var/new_color = input(user, "Select a bulb color", "Select a bulb color", color) as color|null
|
||||
if(!new_color)
|
||||
var/new_color = tgui_input_color(user, "Select a bulb color", "Select a bulb color", color)
|
||||
if(isnull(new_color))
|
||||
return
|
||||
|
||||
// Cancel if they walked away
|
||||
|
||||
@@ -1103,8 +1103,8 @@ GLOBAL_LIST_INIT(view_runtimes_verbs, list(
|
||||
return
|
||||
message = strip_html(message, 500)
|
||||
|
||||
var/message_color = input(src, "Input your message color:", "Color Selector") as color|null
|
||||
if(!message_color)
|
||||
var/message_color = tgui_input_color(src, "Input your message color:", "Admin Message - Color Selector")
|
||||
if(isnull(message_color))
|
||||
return
|
||||
|
||||
show_blurb(about_to_be_banned, 15, message, null, "center", "center", message_color, null, null, 1)
|
||||
|
||||
@@ -329,17 +329,16 @@
|
||||
|
||||
if("hair")
|
||||
if(!(S.bodyflags & BALD))
|
||||
var/input = "Choose your character's hair colour:"
|
||||
var/new_hair = input(user, input, "Character Preference", active_character.h_colour) as color|null
|
||||
if(new_hair)
|
||||
var/new_hair = tgui_input_color(user, "Choose your character's hair color.", "Character Preference", active_character.h_colour)
|
||||
if(!isnull(new_hair))
|
||||
active_character.h_colour = new_hair
|
||||
|
||||
if("secondary_hair")
|
||||
if(!(S.bodyflags & BALD))
|
||||
var/datum/sprite_accessory/hair_style = GLOB.hair_styles_public_list[active_character.h_style]
|
||||
if(hair_style.secondary_theme && !hair_style.no_sec_colour)
|
||||
var/new_hair = input(user, "Choose your character's secondary hair colour:", "Character Preference", active_character.h_sec_colour) as color|null
|
||||
if(new_hair)
|
||||
var/new_hair = tgui_input_color(user, "Choose your character's secondary hair color.", "Character Preference", active_character.h_sec_colour)
|
||||
if(!isnull(new_hair))
|
||||
active_character.h_sec_colour = new_hair
|
||||
|
||||
if("h_style")
|
||||
@@ -387,9 +386,10 @@
|
||||
active_character.h_grad_offset_y = clamp(text2num(expl[2]) || 0, -16, 16)
|
||||
|
||||
if("h_grad_colour")
|
||||
var/result = input(user, "Choose your character's hair gradient colour:", "Character Preference", active_character.h_grad_colour) as color|null
|
||||
if(result)
|
||||
active_character.h_grad_colour = result
|
||||
var/result = tgui_input_color(user, "Choose your character's hair gradient color:", "Character Preference", active_character.h_grad_colour)
|
||||
if(isnull(result))
|
||||
return
|
||||
active_character.h_grad_colour = result
|
||||
|
||||
if("h_grad_alpha")
|
||||
var/result = tgui_input_number(user, "Choose your character's hair gradient alpha (0-255):", "Character Preference", active_character.h_grad_alpha, 255)
|
||||
@@ -398,16 +398,17 @@
|
||||
active_character.h_grad_alpha = clamp(result, 0, 255)
|
||||
|
||||
if("runechat_color")
|
||||
var/result = input(user, "Choose your character's runechat color:", "Character Preference", active_character.runechat_color) as color|null
|
||||
if(result)
|
||||
active_character.runechat_color = result
|
||||
var/result = tgui_input_color(user, "Choose your character's runechat color:", "Character Preference", active_character.runechat_color)
|
||||
if(isnull(result))
|
||||
return
|
||||
active_character.runechat_color = result
|
||||
|
||||
if("headaccessory")
|
||||
if(S.bodyflags & HAS_HEAD_ACCESSORY) //Species with head accessories.
|
||||
var/input = "Choose the colour of your your character's head accessory:"
|
||||
var/new_head_accessory = input(user, input, "Character Preference", active_character.hacc_colour) as color|null
|
||||
if(new_head_accessory)
|
||||
active_character.hacc_colour = new_head_accessory
|
||||
var/new_head_accessory = tgui_input_color(user, "Choose the color of your your character's head accessory.", "Character Preference", active_character.hacc_colour)
|
||||
if(isnull(new_head_accessory))
|
||||
return
|
||||
active_character.hacc_colour = new_head_accessory
|
||||
|
||||
if("ha_style")
|
||||
if(S.bodyflags & HAS_HEAD_ACCESSORY) //Species with head accessories.
|
||||
@@ -485,10 +486,10 @@
|
||||
|
||||
if("m_head_colour")
|
||||
if(S.bodyflags & HAS_HEAD_MARKINGS) //Species with head markings.
|
||||
var/input = "Choose the colour of your your character's head markings:"
|
||||
var/new_markings = input(user, input, "Character Preference", active_character.m_colours["head"]) as color|null
|
||||
if(new_markings)
|
||||
active_character.m_colours["head"] = new_markings
|
||||
var/new_markings = tgui_input_color(user, "Choose the color of your your character's head markings.", "Character Preference", active_character.m_colours["head"])
|
||||
if(isnull(new_markings))
|
||||
return
|
||||
active_character.m_colours["head"] = new_markings
|
||||
|
||||
if("m_style_body")
|
||||
if(S.bodyflags & HAS_BODY_MARKINGS) //Species with body markings/tattoos.
|
||||
@@ -508,10 +509,10 @@
|
||||
|
||||
if("m_body_colour")
|
||||
if(S.bodyflags & HAS_BODY_MARKINGS) //Species with body markings/tattoos.
|
||||
var/input = "Choose the colour of your your character's body markings:"
|
||||
var/new_markings = input(user, input, "Character Preference", active_character.m_colours["body"]) as color|null
|
||||
if(new_markings)
|
||||
active_character.m_colours["body"] = new_markings
|
||||
var/new_markings = tgui_input_color(user, "Choose the color of your your character's body markings.", "Character Preference", active_character.m_colours["body"])
|
||||
if(isnull(new_markings))
|
||||
return
|
||||
active_character.m_colours["body"] = new_markings
|
||||
|
||||
if("m_style_tail")
|
||||
if(S.bodyflags & HAS_TAIL_MARKINGS) //Species with tail markings.
|
||||
@@ -537,10 +538,10 @@
|
||||
|
||||
if("m_tail_colour")
|
||||
if(S.bodyflags & HAS_TAIL_MARKINGS) //Species with tail markings.
|
||||
var/input = "Choose the colour of your your character's tail markings:"
|
||||
var/new_markings = input(user, input, "Character Preference", active_character.m_colours["tail"]) as color|null
|
||||
if(new_markings)
|
||||
active_character.m_colours["tail"] = new_markings
|
||||
var/new_markings = tgui_input_color(user, "Choose the color of your your character's tail markings.", "Character Preference", active_character.m_colours["tail"])
|
||||
if(isnull(new_markings))
|
||||
return
|
||||
active_character.m_colours["tail"] = new_markings
|
||||
|
||||
if("body_accessory")
|
||||
var/list/possible_body_accessories = list()
|
||||
@@ -565,17 +566,19 @@
|
||||
|
||||
if("facial")
|
||||
if(!(S.bodyflags & SHAVED))
|
||||
var/new_facial = input(user, "Choose your character's facial-hair colour:", "Character Preference", active_character.f_colour) as color|null
|
||||
if(new_facial)
|
||||
active_character.f_colour = new_facial
|
||||
var/new_facial = tgui_input_color(user, "Choose your character's facial-hair color:", "Character Preference", active_character.f_colour)
|
||||
if(isnull(new_facial))
|
||||
return
|
||||
active_character.f_colour = new_facial
|
||||
|
||||
if("secondary_facial")
|
||||
if(!(S.bodyflags & SHAVED))
|
||||
var/datum/sprite_accessory/facial_hair_style = GLOB.facial_hair_styles_list[active_character.f_style]
|
||||
if(facial_hair_style.secondary_theme && !facial_hair_style.no_sec_colour)
|
||||
var/new_facial = input(user, "Choose your character's secondary facial-hair colour:", "Character Preference", active_character.f_sec_colour) as color|null
|
||||
if(new_facial)
|
||||
active_character.f_sec_colour = new_facial
|
||||
var/new_facial = tgui_input_color(user, "Choose your character's secondary facial-hair color:", "Character Preference", active_character.f_sec_colour)
|
||||
if(isnull(new_facial))
|
||||
return
|
||||
active_character.f_sec_colour = new_facial
|
||||
|
||||
if("f_style")
|
||||
var/list/valid_facial_hairstyles = list()
|
||||
@@ -658,34 +661,37 @@
|
||||
active_character.socks = new_socks
|
||||
|
||||
if("eyes")
|
||||
var/new_eyes = input(user, "Choose your character's eye colour:", "Character Preference", active_character.e_colour) as color|null
|
||||
if(new_eyes)
|
||||
active_character.e_colour = new_eyes
|
||||
var/new_eyes = tgui_input_color(user, "Choose your character's eye color:", "Character Preference", active_character.e_colour)
|
||||
if(isnull(new_eyes))
|
||||
return
|
||||
active_character.e_colour = new_eyes
|
||||
|
||||
if("s_tone")
|
||||
if(S.bodyflags & HAS_SKIN_TONE)
|
||||
var/new_s_tone = input(user, "Choose your character's skin-tone:\n(Light 1 - 220 Dark)", "Character Preference") as num|null
|
||||
if(!new_s_tone)
|
||||
if(isnull(new_s_tone))
|
||||
return
|
||||
active_character.s_tone = 35 - max(min(round(new_s_tone), 220), 1)
|
||||
else if(S.bodyflags & HAS_ICON_SKIN_TONE)
|
||||
var/const/MAX_LINE_ENTRIES = 4
|
||||
var/prompt = "Choose your character's skin tone: 1-[length(S.icon_skin_tones)]\n(Light to Dark)"
|
||||
var/skin_c = tgui_input_number(user, prompt, "Character Preference", active_character.s_tone, length(S.icon_skin_tones), 1)
|
||||
if(!skin_c)
|
||||
if(isnull(skin_c))
|
||||
return
|
||||
active_character.s_tone = skin_c
|
||||
|
||||
if("skin")
|
||||
if((S.bodyflags & HAS_SKIN_COLOR) || GLOB.body_accessory_by_species[active_character.species] || check_rights(R_ADMIN, 0, user))
|
||||
var/new_skin = input(user, "Choose your character's skin colour: ", "Character Preference", active_character.s_colour) as color|null
|
||||
if(new_skin)
|
||||
active_character.s_colour = new_skin
|
||||
var/new_skin = tgui_input_color(user, "Choose your character's skin color: ", "Character Preference", active_character.s_colour)
|
||||
if(isnull(new_skin))
|
||||
return
|
||||
active_character.s_colour = new_skin
|
||||
|
||||
if("ooccolor")
|
||||
var/new_ooccolor = input(user, "Choose your OOC colour:", "Game Preference", ooccolor) as color|null
|
||||
if(new_ooccolor)
|
||||
ooccolor = new_ooccolor
|
||||
var/new_ooccolor = tgui_input_color(user, "Choose your OOC color:", "Game Preference", ooccolor)
|
||||
if(isnull(new_ooccolor))
|
||||
return
|
||||
ooccolor = new_ooccolor
|
||||
|
||||
if("bag")
|
||||
var/new_backbag = tgui_input_list(user, "Choose your character's style of bag", "Character Preference", GLOB.backbaglist)
|
||||
@@ -1015,8 +1021,9 @@
|
||||
toggles2 ^= PREFTOGGLE_2_AFKWATCH
|
||||
|
||||
if("UIcolor")
|
||||
var/UI_style_color_new = input(user, "Choose your UI color, dark colors are not recommended!", UI_style_color) as color|null
|
||||
if(!UI_style_color_new) return
|
||||
var/UI_style_color_new = tgui_input_color(user, "Choose your UI color, dark colors are not recommended!", "Game Preference", UI_style_color)
|
||||
if(isnull(UI_style_color_new))
|
||||
return
|
||||
UI_style_color = UI_style_color_new
|
||||
|
||||
if(ishuman(usr)) //mid-round preference changes, for aesthetics
|
||||
@@ -1151,9 +1158,10 @@
|
||||
screentip_mode = desired_screentip_mode
|
||||
|
||||
if("screentip_color")
|
||||
var/screentip_color_new = input(user, "Choose your screentip color", screentip_color) as color|null
|
||||
if(screentip_color_new)
|
||||
screentip_color = screentip_color_new
|
||||
var/screentip_color_new = tgui_input_color(user, "Choose your screentip color", "Game Preference", screentip_color)
|
||||
if(isnull(screentip_color_new))
|
||||
return
|
||||
screentip_color = screentip_color_new
|
||||
|
||||
if("edit_2fa")
|
||||
// Do this async so we arent holding up a topic() call
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
/datum/gear_tweak/color/get_metadata(user, metadata)
|
||||
if(valid_colors)
|
||||
return input(user, "Choose an item color.", "Character Preference", metadata) as null|anything in valid_colors
|
||||
return input(user, "Choose an item color.", "Global Preference", metadata) as color|null
|
||||
return tgui_input_color(user, "Choose an item color.", "Global Preference", metadata)
|
||||
|
||||
/datum/gear_tweak/color/tweak_item(obj/item/I, metadata)
|
||||
if(valid_colors && !(metadata in valid_colors))
|
||||
|
||||
@@ -510,8 +510,8 @@
|
||||
blackbox_message = "Set Own OOC"
|
||||
|
||||
/datum/preference_toggle/special_toggle/set_ooc_color/set_toggles(client/user)
|
||||
var/new_ooccolor = input(usr, "Please select your OOC color.", "OOC color", user.prefs.ooccolor) as color|null
|
||||
if(new_ooccolor)
|
||||
var/new_ooccolor = tgui_input_color(usr, "Please select your OOC color.", "OOC Color", user.prefs.ooccolor)
|
||||
if(!isnull(new_ooccolor))
|
||||
user.prefs.ooccolor = new_ooccolor
|
||||
to_chat(usr, "Your OOC color has been set to [new_ooccolor].")
|
||||
else
|
||||
|
||||
@@ -100,8 +100,8 @@
|
||||
|
||||
/obj/item/fluff/tattoo_gun/elliot_cybernetic_tat/attack_self(mob/user as mob)
|
||||
if(!used)
|
||||
var/ink_color = input("Please select an ink color.", "Tattoo Ink Color", rgb(tattoo_r, tattoo_g, tattoo_b)) as color|null
|
||||
if(ink_color && !(user.incapacitated() || used))
|
||||
var/ink_color = tgui_input_color(user, "Please select an ink color.", "Tattoo Ink Color", rgb(tattoo_r, tattoo_g, tattoo_b))
|
||||
if(!isnull(ink_color) && !(user.incapacitated() || used))
|
||||
tattoo_r = color2R(ink_color)
|
||||
tattoo_g = color2G(ink_color)
|
||||
tattoo_b = color2B(ink_color)
|
||||
|
||||
@@ -152,11 +152,11 @@
|
||||
if(!head_organ)
|
||||
return
|
||||
if(!robohead.is_monitor) //If they've got a prosthetic head and it isn't a monitor, they've no screen to adjust. Instead, let them change the colour of their optics!
|
||||
var/optic_colour = input(H, "Select optic colour", H.m_colours["head"]) as color|null
|
||||
var/optic_colour = tgui_input_color(H, "Please select an optic color", "Select Optic Color", H.m_colours["head"])
|
||||
if(H.incapacitated(TRUE, TRUE))
|
||||
to_chat(H, "<span class='warning'>You were interrupted while changing the colour of your optics.</span>")
|
||||
to_chat(H, "<span class='warning'>You were interrupted while changing the color of your optics.</span>")
|
||||
return
|
||||
if(optic_colour)
|
||||
if(!isnull(optic_colour))
|
||||
H.change_markings(optic_colour, "head")
|
||||
|
||||
else if(robohead.is_monitor) //Means that the character's head is a monitor (has a screen). Time to customize.
|
||||
@@ -175,7 +175,7 @@
|
||||
var/new_style = tgui_input_list(H, "Select a monitor display", "Monitor Display", hair)
|
||||
if(!new_style)
|
||||
return
|
||||
var/new_color = input("Please select hair color.", "Monitor Color", head_organ.hair_colour) as null|color
|
||||
var/new_color = tgui_input_color(H, "Please select hair color.", "Monitor Color", head_organ.hair_colour)
|
||||
|
||||
if(H.incapacitated(TRUE, TRUE))
|
||||
to_chat(H, "<span class='warning'>You were interrupted while changing your monitor display.</span>")
|
||||
@@ -183,7 +183,7 @@
|
||||
|
||||
if(new_style)
|
||||
H.change_hair(new_style, 1) // The 1 is to enable custom sprites
|
||||
if(new_color)
|
||||
if(!isnull(new_color))
|
||||
H.change_hair_color(new_color)
|
||||
|
||||
/datum/species/machine/spec_electrocute_act(mob/living/carbon/human/H, shock_damage, source, siemens_coeff, flags)
|
||||
|
||||
@@ -251,8 +251,8 @@
|
||||
/obj/item/mod/module/flashlight/configure_edit(key, value)
|
||||
switch(key)
|
||||
if("light_color")
|
||||
value = input(usr, "Pick new light color", "Flashlight Color") as color|null
|
||||
if(!value)
|
||||
value = tgui_input_color(usr, "Pick new light color", "Flashlight Color", light_color)
|
||||
if(isnull(value))
|
||||
return
|
||||
if(is_color_dark(value, 50))
|
||||
to_chat(mod.wearer, ("<span class='warning'>That is too dark</span>"))
|
||||
|
||||
@@ -657,4 +657,4 @@
|
||||
desc = "Causes kinetic accelerator bolts to have an adjustable-colored tracer trail and explosion. Use in-hand to change color."
|
||||
|
||||
/obj/item/borg/upgrade/modkit/tracer/adjustable/attack_self(mob/user)
|
||||
bolt_color = input(user,"","Choose Color",bolt_color) as color|null
|
||||
bolt_color = tgui_input_color(user, "Please select a tracer color", "PKA Tracer Color", bolt_color)
|
||||
|
||||
@@ -72,8 +72,8 @@
|
||||
|
||||
if("skin_color")
|
||||
if(can_change_skin_color())
|
||||
var/new_skin = input(usr, "Choose your character's skin colour: ", "Skin Color", owner.skin_colour) as color|null
|
||||
if(new_skin && (!..()) && owner.change_skin_color(new_skin))
|
||||
var/new_skin = tgui_input_color(usr, "Choose your character's skin colour: ", "Skin Color", owner.skin_colour)
|
||||
if(!isnull(new_skin) && (!..()) && owner.change_skin_color(new_skin))
|
||||
update_dna()
|
||||
|
||||
if("hair")
|
||||
@@ -83,32 +83,32 @@
|
||||
|
||||
if("hair_color")
|
||||
if(can_change(APPEARANCE_HAIR_COLOR))
|
||||
var/new_hair = input("Please select hair color.", "Hair Color", head_organ.hair_colour) as color|null
|
||||
if(new_hair && (!..()) && owner.change_hair_color(new_hair))
|
||||
var/new_hair = tgui_input_color(usr, "Please select hair color.", "Hair Color", head_organ.hair_colour)
|
||||
if(!isnull(new_hair) && (!..()) && owner.change_hair_color(new_hair))
|
||||
update_dna()
|
||||
|
||||
if("secondary_hair_color")
|
||||
if(can_change(APPEARANCE_SECONDARY_HAIR_COLOR))
|
||||
var/new_hair = input("Please select secondary hair color.", "Secondary Hair Color", head_organ.sec_hair_colour) as color|null
|
||||
if(new_hair && (!..()) && owner.change_hair_color(new_hair, 1))
|
||||
var/new_hair = tgui_input_color(usr, "Please select secondary hair color.", "Secondary Hair Color", head_organ.sec_hair_colour)
|
||||
if(!isnull(new_hair) && (!..()) && owner.change_hair_color(new_hair, 1))
|
||||
update_dna()
|
||||
|
||||
if("hair_gradient")
|
||||
if(can_change(APPEARANCE_HAIR) && length(valid_hairstyles))
|
||||
var/new_style = tgui_input_list(usr, "Please select gradient style", "Hair Gradient", GLOB.hair_gradients_list)
|
||||
if(new_style)
|
||||
if(!isnull(new_style))
|
||||
owner.change_hair_gradient(style = new_style)
|
||||
|
||||
if("hair_gradient_offset")
|
||||
if(can_change(APPEARANCE_HAIR) && length(valid_hairstyles))
|
||||
var/new_offset = input("Please enter gradient offset as a comma-separated value (x,y). Example:\n0,0 (no offset)\n5,0 (5 pixels to the right)", "Hair Gradient", "[head_organ.h_grad_offset_x],[head_organ.h_grad_offset_y]") as null|text
|
||||
if(new_offset)
|
||||
if(!isnull(new_offset))
|
||||
owner.change_hair_gradient(offset_raw = new_offset)
|
||||
|
||||
if("hair_gradient_colour")
|
||||
if(can_change(APPEARANCE_HAIR) && length(valid_hairstyles))
|
||||
var/new_color = input("Please select gradient color.", "Hair Gradient", head_organ.h_grad_colour) as null|color
|
||||
if(new_color)
|
||||
var/new_color = tgui_input_color(usr, "Please select gradient color.", "Hair Gradient", head_organ.h_grad_colour)
|
||||
if(!isnull(new_color))
|
||||
owner.change_hair_gradient(color = new_color)
|
||||
|
||||
if("hair_gradient_alpha")
|
||||
@@ -124,26 +124,26 @@
|
||||
|
||||
if("facial_hair_color")
|
||||
if(can_change(APPEARANCE_FACIAL_HAIR_COLOR))
|
||||
var/new_facial = input("Please select facial hair color.", "Facial Hair Color", head_organ.facial_colour) as color|null
|
||||
if(new_facial && (!..()) && owner.change_facial_hair_color(new_facial))
|
||||
var/new_facial = tgui_input_color(usr, "Please select facial hair color.", "Facial Hair Color", head_organ.facial_colour)
|
||||
if(!isnull(new_facial) && (!..()) && owner.change_facial_hair_color(new_facial))
|
||||
update_dna()
|
||||
|
||||
if("secondary_facial_hair_color")
|
||||
if(can_change(APPEARANCE_SECONDARY_FACIAL_HAIR_COLOR))
|
||||
var/new_facial = input("Please select secondary facial hair color.", "Secondary Facial Hair Color", head_organ.sec_facial_colour) as color|null
|
||||
if(new_facial && (!..()) && owner.change_facial_hair_color(new_facial, 1))
|
||||
var/new_facial = tgui_input_color(usr, "Please select secondary facial hair color.", "Secondary Facial Hair Color", head_organ.sec_facial_colour)
|
||||
if(!isnull(new_facial) && (!..()) && owner.change_facial_hair_color(new_facial, 1))
|
||||
update_dna()
|
||||
|
||||
if("eye_color")
|
||||
if(can_change(APPEARANCE_EYE_COLOR))
|
||||
var/obj/item/organ/internal/eyes/eyes_organ = owner.get_int_organ(/obj/item/organ/internal/eyes)
|
||||
var/new_eyes = input("Please select eye color.", "Eye Color", eyes_organ.eye_color) as color|null
|
||||
if(new_eyes && (!..()) && owner.change_eye_color(new_eyes))
|
||||
var/new_eyes = tgui_input_color(usr, "Please select eye color.", "Eye Color", eyes_organ.eye_color)
|
||||
if(!isnull(new_eyes) && (!..()) && owner.change_eye_color(new_eyes))
|
||||
update_dna()
|
||||
|
||||
if("runechat_color")
|
||||
var/new_runechat_color = input("Please select runechat color.", "Runechat Color", owner.dna.chat_color) as color|null
|
||||
if(new_runechat_color && (!..()))
|
||||
var/new_runechat_color = tgui_input_color("Please select runechat color.", "Runechat Color", owner.dna.chat_color)
|
||||
if(!isnull(new_runechat_color) && (!..()))
|
||||
owner.change_runechat_color(new_runechat_color)
|
||||
|
||||
if("head_accessory")
|
||||
@@ -153,8 +153,8 @@
|
||||
|
||||
if("head_accessory_color")
|
||||
if(can_change_head_accessory())
|
||||
var/new_head_accessory = input("Please select head accessory color.", "Head Accessory Color", head_organ.headacc_colour) as color|null
|
||||
if(new_head_accessory && (!..()) && owner.change_head_accessory_color(new_head_accessory))
|
||||
var/new_head_accessory = tgui_input_color(usr, "Please select head accessory color.", "Head Accessory Color", head_organ.headacc_colour)
|
||||
if(!isnull(new_head_accessory) && (!..()) && owner.change_head_accessory_color(new_head_accessory))
|
||||
update_dna()
|
||||
|
||||
if("head_marking")
|
||||
@@ -164,7 +164,7 @@
|
||||
|
||||
if("head_marking_color")
|
||||
if(can_change_markings("head"))
|
||||
var/new_markings = input("Please select head marking color.", "Marking Color", owner.m_colours["head"]) as color|null
|
||||
var/new_markings = tgui_input_color(usr, "Please select head marking color.", "Marking Color", owner.m_colours["head"])
|
||||
if(new_markings && (!..()) && owner.change_marking_color(new_markings, "head"))
|
||||
update_dna()
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
|
||||
if("body_marking_color")
|
||||
if(can_change_markings("body"))
|
||||
var/new_markings = input("Please select body marking color.", "Marking Color", owner.m_colours["body"]) as color|null
|
||||
var/new_markings = tgui_input_color(usr, "Please select body marking color.", "Marking Color", owner.m_colours["body"])
|
||||
if(new_markings && (!..()) && owner.change_marking_color(new_markings, "body"))
|
||||
update_dna()
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
|
||||
if("tail_marking_color")
|
||||
if(can_change_markings("tail"))
|
||||
var/new_markings = input("Please select tail marking color.", "Marking Color", owner.m_colours["tail"]) as color|null
|
||||
var/new_markings = tgui_input_color(usr, "Please select tail marking color.", "Marking Color", owner.m_colours["tail"])
|
||||
if(new_markings && (!..()) && owner.change_marking_color(new_markings, "tail"))
|
||||
update_dna()
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Creates a TGUI color picker window and returns the user's response.
|
||||
*
|
||||
* This proc should be used to create a color picker that the caller will wait for a response from.
|
||||
* Arguments:
|
||||
* * user - The user to show the picker to.
|
||||
* * title - The of the picker modal, shown on the top of the TGUI window.
|
||||
* * timeout - The timeout of the picker, after which the modal will close and qdel itself. Set to zero for no timeout.
|
||||
* * autofocus - The bool that controls if this picker should grab window focus.
|
||||
*/
|
||||
/proc/tgui_input_color(mob/user, message, title, default = "#000000", timeout = 0, autofocus = TRUE, ui_state = GLOB.always_state)
|
||||
if(!user)
|
||||
user = usr
|
||||
if(!istype(user))
|
||||
if(!isclient(user))
|
||||
CRASH("We passed something that wasn't a user/client in a TGUI Input Color! The passed thing was [user]!")
|
||||
var/client/client = user
|
||||
user = client.mob
|
||||
|
||||
if(isnull(user.client))
|
||||
return
|
||||
|
||||
// Client does NOT have tgui_input on: Returns regular input
|
||||
if(user.client?.prefs?.toggles2 & PREFTOGGLE_2_DISABLE_TGUI_INPUT)
|
||||
return input(user, message, title, default) as color|null
|
||||
|
||||
var/datum/tgui_input_color/picker = new(user, message, title, default, timeout, autofocus, ui_state)
|
||||
picker.ui_interact(user)
|
||||
picker.wait()
|
||||
if(picker)
|
||||
. = picker.choice
|
||||
qdel(picker)
|
||||
|
||||
/**
|
||||
* tgui_input_color
|
||||
*
|
||||
* Datum used for instantiating and using a TGUI-controlled color picker.
|
||||
*/
|
||||
/datum/tgui_input_color
|
||||
/// The title of the TGUI window
|
||||
var/title
|
||||
/// The message to show the user
|
||||
var/message
|
||||
/// The default choice, used if there is an existing value
|
||||
var/default
|
||||
/// The color the user selected, null if no selection has been made
|
||||
var/choice
|
||||
/// The time at which the tgui_input_color was created, for displaying timeout progress.
|
||||
var/start_time
|
||||
/// The lifespan of the tgui_input_color, after which the window will close and delete itself.
|
||||
var/timeout
|
||||
/// The bool that controls if this modal should grab window focus
|
||||
var/autofocus
|
||||
/// Boolean field describing if the tgui_input_color was closed by the user.
|
||||
var/closed
|
||||
/// The attached timer that handles this objects timeout deletion
|
||||
var/deletion_timer
|
||||
/// The TGUI UI state that will be returned in ui_state(). Default: always_state
|
||||
var/datum/ui_state/state
|
||||
|
||||
/datum/tgui_input_color/New(mob/user, message, title, default, timeout, autofocus, ui_state)
|
||||
src.autofocus = autofocus
|
||||
src.title = title
|
||||
src.default = default
|
||||
src.message = message
|
||||
src.state = ui_state
|
||||
|
||||
if(timeout)
|
||||
src.timeout = timeout
|
||||
start_time = world.time
|
||||
deletion_timer = QDEL_IN(src, timeout)
|
||||
|
||||
/datum/tgui_input_color/Destroy(force, ...)
|
||||
SStgui.close_uis(src)
|
||||
state = null
|
||||
deltimer(deletion_timer)
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Waits for a user's response to the tgui_input_color's prompt before returning. Returns early if
|
||||
* the window was closed by the user.
|
||||
*/
|
||||
/datum/tgui_input_color/proc/wait()
|
||||
while(!choice && !closed && !QDELETED(src))
|
||||
stoplag(1)
|
||||
|
||||
/datum/tgui_input_color/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "ColorPickerModal")
|
||||
ui.open()
|
||||
ui.set_autoupdate(timeout > 0)
|
||||
|
||||
/datum/tgui_input_color/ui_close(mob/user)
|
||||
closed = TRUE
|
||||
|
||||
/datum/tgui_input_color/ui_state(mob/user)
|
||||
return state
|
||||
|
||||
/datum/tgui_input_color/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["autofocus"] = autofocus
|
||||
data["large_buttons"] = !user.client?.prefs || (user.client.prefs.toggles2 & PREFTOGGLE_2_LARGE_INPUT_BUTTONS)
|
||||
data["swapped_buttons"] = !user.client?.prefs || (user.client.prefs.toggles2 & PREFTOGGLE_2_SWAP_INPUT_BUTTONS)
|
||||
data["title"] = title
|
||||
data["default_color"] = default
|
||||
data["message"] = message
|
||||
return data
|
||||
|
||||
/datum/tgui_input_color/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
if(timeout)
|
||||
data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS))
|
||||
return data
|
||||
|
||||
/datum/tgui_input_color/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
switch(action)
|
||||
if("submit")
|
||||
if(!findtext(params["entry"], GLOB.is_color))
|
||||
return
|
||||
choice = params["entry"]
|
||||
closed = TRUE
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
if("cancel")
|
||||
closed = TRUE
|
||||
SStgui.close_uis(src)
|
||||
return TRUE
|
||||
@@ -2953,6 +2953,7 @@
|
||||
#include "code\modules\tgui\states\strippable_state.dm"
|
||||
#include "code\modules\tgui\states\viewer_state.dm"
|
||||
#include "code\modules\tgui\tgui_input\alert_input.dm"
|
||||
#include "code\modules\tgui\tgui_input\color_input.dm"
|
||||
#include "code\modules\tgui\tgui_input\keycombo_input.dm"
|
||||
#include "code\modules\tgui\tgui_input\list_input.dm"
|
||||
#include "code\modules\tgui\tgui_input\number_input.dm"
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2020 Aleksej Komarov
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const EPSILON = 0.0001;
|
||||
|
||||
export class Color {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
a: number;
|
||||
|
||||
constructor(r = 0, g = 0, b = 0, a = 1) {
|
||||
this.r = r;
|
||||
this.g = g;
|
||||
this.b = b;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `rgba(${this.r | 0}, ${this.g | 0}, ${this.b | 0}, ${this.a | 0})`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a color from the CSS hex color notation.
|
||||
*/
|
||||
static fromHex(hex: string): Color {
|
||||
return new Color(parseInt(hex.substr(1, 2), 16), parseInt(hex.substr(3, 2), 16), parseInt(hex.substr(5, 2), 16));
|
||||
}
|
||||
|
||||
/**
|
||||
* Linear interpolation of two colors.
|
||||
*/
|
||||
static lerp(c1: Color, c2: Color, n: number): Color {
|
||||
return new Color(
|
||||
(c2.r - c1.r) * n + c1.r,
|
||||
(c2.g - c1.g) * n + c1.g,
|
||||
(c2.b - c1.b) * n + c1.b,
|
||||
(c2.a - c1.a) * n + c1.a
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops up the color in the provided list of colors
|
||||
* with linear interpolation.
|
||||
*/
|
||||
static lookup(value: number, colors: Color[] = []): Color {
|
||||
const len = colors.length;
|
||||
if (len < 2) {
|
||||
throw new Error('Needs at least two colors!');
|
||||
}
|
||||
const scaled = value * (len - 1);
|
||||
if (value < EPSILON) {
|
||||
return colors[0];
|
||||
}
|
||||
if (value >= 1 - EPSILON) {
|
||||
return colors[len - 1];
|
||||
}
|
||||
const ratio = scaled % 1;
|
||||
const index = scaled | 0;
|
||||
return Color.lerp(colors[index], colors[index + 1], ratio);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* MIT License
|
||||
* https://github.com/omgovich/react-colorful/
|
||||
*
|
||||
* Copyright (c) 2020 Vlad Shilov <omgovich@ya.ru>
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
const round = (number: number, digits = 0, base = Math.pow(10, digits)): number => {
|
||||
return Math.round(base * number) / base;
|
||||
};
|
||||
|
||||
export interface RgbColor {
|
||||
r: number;
|
||||
g: number;
|
||||
b: number;
|
||||
}
|
||||
|
||||
export interface RgbaColor extends RgbColor {
|
||||
a: number;
|
||||
}
|
||||
|
||||
export interface HslColor {
|
||||
h: number;
|
||||
s: number;
|
||||
l: number;
|
||||
}
|
||||
|
||||
export interface HslaColor extends HslColor {
|
||||
a: number;
|
||||
}
|
||||
|
||||
export interface HsvColor {
|
||||
h: number;
|
||||
s: number;
|
||||
v: number;
|
||||
}
|
||||
|
||||
export interface HsvaColor extends HsvColor {
|
||||
a: number;
|
||||
}
|
||||
|
||||
export type ObjectColor = RgbColor | HslColor | HsvColor | RgbaColor | HslaColor | HsvaColor;
|
||||
|
||||
export type AnyColor = string | ObjectColor;
|
||||
|
||||
/**
|
||||
* Valid CSS <angle> units.
|
||||
* https://developer.mozilla.org/en-US/docs/Web/CSS/angle
|
||||
*/
|
||||
const angleUnits: Record<string, number> = {
|
||||
grad: 360 / 400,
|
||||
turn: 360,
|
||||
rad: 360 / (Math.PI * 2),
|
||||
};
|
||||
|
||||
export const hexToHsva = (hex: string): HsvaColor => rgbaToHsva(hexToRgba(hex));
|
||||
|
||||
export const hexToRgba = (hex: string): RgbaColor => {
|
||||
if (hex[0] === '#') hex = hex.substring(1);
|
||||
|
||||
if (hex.length < 6) {
|
||||
return {
|
||||
r: parseInt(hex[0] + hex[0], 16),
|
||||
g: parseInt(hex[1] + hex[1], 16),
|
||||
b: parseInt(hex[2] + hex[2], 16),
|
||||
a: hex.length === 4 ? round(parseInt(hex[3] + hex[3], 16) / 255, 2) : 1,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
r: parseInt(hex.substring(0, 2), 16),
|
||||
g: parseInt(hex.substring(2, 4), 16),
|
||||
b: parseInt(hex.substring(4, 6), 16),
|
||||
a: hex.length === 8 ? round(parseInt(hex.substring(6, 8), 16) / 255, 2) : 1,
|
||||
};
|
||||
};
|
||||
|
||||
export const parseHue = (value: string, unit = 'deg'): number => {
|
||||
return Number(value) * (angleUnits[unit] || 1);
|
||||
};
|
||||
|
||||
export const hslaStringToHsva = (hslString: string): HsvaColor => {
|
||||
const matcher =
|
||||
/hsla?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
|
||||
const match = matcher.exec(hslString);
|
||||
|
||||
if (!match) return { h: 0, s: 0, v: 0, a: 1 };
|
||||
|
||||
return hslaToHsva({
|
||||
h: parseHue(match[1], match[2]),
|
||||
s: Number(match[3]),
|
||||
l: Number(match[4]),
|
||||
a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),
|
||||
});
|
||||
};
|
||||
|
||||
export const hslStringToHsva = hslaStringToHsva;
|
||||
|
||||
export const hslaToHsva = ({ h, s, l, a }: HslaColor): HsvaColor => {
|
||||
s *= (l < 50 ? l : 100 - l) / 100;
|
||||
|
||||
return {
|
||||
h: h,
|
||||
s: s > 0 ? ((2 * s) / (l + s)) * 100 : 0,
|
||||
v: l + s,
|
||||
a,
|
||||
};
|
||||
};
|
||||
|
||||
export const hsvaToHex = (hsva: HsvaColor): string => rgbaToHex(hsvaToRgba(hsva));
|
||||
|
||||
export const hsvaToHsla = ({ h, s, v, a }: HsvaColor): HslaColor => {
|
||||
const hh = ((200 - s) * v) / 100;
|
||||
|
||||
return {
|
||||
h: round(h),
|
||||
s: round(hh > 0 && hh < 200 ? ((s * v) / 100 / (hh <= 100 ? hh : 200 - hh)) * 100 : 0),
|
||||
l: round(hh / 2),
|
||||
a: round(a, 2),
|
||||
};
|
||||
};
|
||||
|
||||
export const hsvaToHslString = (hsva: HsvaColor): string => {
|
||||
const { h, s, l } = hsvaToHsla(hsva);
|
||||
return `hsl(${h}, ${s}%, ${l}%)`;
|
||||
};
|
||||
|
||||
export const hsvaToHsvString = (hsva: HsvaColor): string => {
|
||||
const { h, s, v } = roundHsva(hsva);
|
||||
return `hsv(${h}, ${s}%, ${v}%)`;
|
||||
};
|
||||
|
||||
export const hsvaToHsvaString = (hsva: HsvaColor): string => {
|
||||
const { h, s, v, a } = roundHsva(hsva);
|
||||
return `hsva(${h}, ${s}%, ${v}%, ${a})`;
|
||||
};
|
||||
|
||||
export const hsvaToHslaString = (hsva: HsvaColor): string => {
|
||||
const { h, s, l, a } = hsvaToHsla(hsva);
|
||||
return `hsla(${h}, ${s}%, ${l}%, ${a})`;
|
||||
};
|
||||
|
||||
export const hsvaToRgba = ({ h, s, v, a }: HsvaColor): RgbaColor => {
|
||||
h = (h / 360) * 6;
|
||||
s = s / 100;
|
||||
v = v / 100;
|
||||
|
||||
const hh = Math.floor(h),
|
||||
b = v * (1 - s),
|
||||
c = v * (1 - (h - hh) * s),
|
||||
d = v * (1 - (1 - h + hh) * s),
|
||||
module = hh % 6;
|
||||
|
||||
return {
|
||||
r: [v, c, b, b, d, v][module] * 255,
|
||||
g: [d, v, v, c, b, b][module] * 255,
|
||||
b: [b, b, d, v, v, c][module] * 255,
|
||||
a: round(a, 2),
|
||||
};
|
||||
};
|
||||
|
||||
export const hsvaToRgbString = (hsva: HsvaColor): string => {
|
||||
const { r, g, b } = hsvaToRgba(hsva);
|
||||
return `rgb(${round(r)}, ${round(g)}, ${round(b)})`;
|
||||
};
|
||||
|
||||
export const hsvaToRgbaString = (hsva: HsvaColor): string => {
|
||||
const { r, g, b, a } = hsvaToRgba(hsva);
|
||||
return `rgba(${round(r)}, ${round(g)}, ${round(b)}, ${round(a, 2)})`;
|
||||
};
|
||||
|
||||
export const hsvaStringToHsva = (hsvString: string): HsvaColor => {
|
||||
const matcher =
|
||||
/hsva?\(?\s*(-?\d*\.?\d+)(deg|rad|grad|turn)?[,\s]+(-?\d*\.?\d+)%?[,\s]+(-?\d*\.?\d+)%?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
|
||||
const match = matcher.exec(hsvString);
|
||||
|
||||
if (!match) return { h: 0, s: 0, v: 0, a: 1 };
|
||||
|
||||
return roundHsva({
|
||||
h: parseHue(match[1], match[2]),
|
||||
s: Number(match[3]),
|
||||
v: Number(match[4]),
|
||||
a: match[5] === undefined ? 1 : Number(match[5]) / (match[6] ? 100 : 1),
|
||||
});
|
||||
};
|
||||
|
||||
export const hsvStringToHsva = hsvaStringToHsva;
|
||||
|
||||
export const rgbaStringToHsva = (rgbaString: string): HsvaColor => {
|
||||
const matcher =
|
||||
/rgba?\(?\s*(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?[,\s]+(-?\d*\.?\d+)(%)?,?\s*[/\s]*(-?\d*\.?\d+)?(%)?\s*\)?/i;
|
||||
const match = matcher.exec(rgbaString);
|
||||
|
||||
if (!match) return { h: 0, s: 0, v: 0, a: 1 };
|
||||
|
||||
return rgbaToHsva({
|
||||
r: Number(match[1]) / (match[2] ? 100 / 255 : 1),
|
||||
g: Number(match[3]) / (match[4] ? 100 / 255 : 1),
|
||||
b: Number(match[5]) / (match[6] ? 100 / 255 : 1),
|
||||
a: match[7] === undefined ? 1 : Number(match[7]) / (match[8] ? 100 : 1),
|
||||
});
|
||||
};
|
||||
|
||||
export const rgbStringToHsva = rgbaStringToHsva;
|
||||
|
||||
const format = (number: number) => {
|
||||
const hex = number.toString(16);
|
||||
return hex.length < 2 ? '0' + hex : hex;
|
||||
};
|
||||
|
||||
export const rgbaToHex = ({ r, g, b, a }: RgbaColor): string => {
|
||||
const alphaHex = a < 1 ? format(round(a * 255)) : '';
|
||||
return '#' + format(round(r)) + format(round(g)) + format(round(b)) + alphaHex;
|
||||
};
|
||||
|
||||
export const rgbaToHsva = ({ r, g, b, a }: RgbaColor): HsvaColor => {
|
||||
const max = Math.max(r, g, b);
|
||||
const delta = max - Math.min(r, g, b);
|
||||
|
||||
// prettier-ignore
|
||||
const hh = delta
|
||||
? max === r
|
||||
? (g - b) / delta
|
||||
: max === g
|
||||
? 2 + (b - r) / delta
|
||||
: 4 + (r - g) / delta
|
||||
: 0;
|
||||
|
||||
return {
|
||||
h: 60 * (hh < 0 ? hh + 6 : hh),
|
||||
s: max ? (delta / max) * 100 : 0,
|
||||
v: (max / 255) * 100,
|
||||
a,
|
||||
};
|
||||
};
|
||||
|
||||
export const roundHsva = (hsva: HsvaColor): HsvaColor => ({
|
||||
h: round(hsva.h),
|
||||
s: round(hsva.s),
|
||||
v: round(hsva.v),
|
||||
a: round(hsva.a, 2),
|
||||
});
|
||||
|
||||
export const rgbaToRgb = ({ r, g, b }: RgbaColor): RgbColor => ({ r, g, b });
|
||||
|
||||
export const hslaToHsl = ({ h, s, l }: HslaColor): HslColor => ({ h, s, l });
|
||||
|
||||
export const hsvaToHsv = (hsva: HsvaColor): HsvColor => {
|
||||
const { h, s, v } = roundHsva(hsva);
|
||||
return { h, s, v };
|
||||
};
|
||||
|
||||
const hexMatcher = /^#?([0-9A-F]{3,8})$/i;
|
||||
|
||||
export const validHex = (value: string, alpha?: boolean): boolean => {
|
||||
const match = hexMatcher.exec(value);
|
||||
const length = match ? match[1].length : 0;
|
||||
|
||||
return (
|
||||
length === 3 || // '#rgb' format
|
||||
length === 6 || // '#rrggbb' format
|
||||
(!!alpha && length === 4) || // '#rgba' format
|
||||
(!!alpha && length === 8) // '#rrggbbaa' format
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* MIT License
|
||||
* https://github.com/omgovich/react-colorful/
|
||||
*
|
||||
* Copyright (c) 2020 Vlad Shilov <omgovich@ya.ru>
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import { clamp } from 'common/math';
|
||||
import { Component, InfernoNode, createRef, RefObject } from 'inferno';
|
||||
|
||||
export interface Interaction {
|
||||
left: number;
|
||||
top: number;
|
||||
}
|
||||
|
||||
// Finds the proper window object to fix iframe embedding issues
|
||||
const getParentWindow = (node?: HTMLDivElement | null): Window => {
|
||||
return (node && node.ownerDocument.defaultView) || self;
|
||||
};
|
||||
|
||||
// Returns a relative position of the pointer inside the node's bounding box
|
||||
const getRelativePosition = (node: HTMLDivElement, event: MouseEvent): Interaction => {
|
||||
const rect = node.getBoundingClientRect();
|
||||
const pointer = event as MouseEvent;
|
||||
return {
|
||||
left: clamp((pointer.pageX - (rect.left + getParentWindow(node).pageXOffset)) / rect.width, 0, 1),
|
||||
top: clamp((pointer.pageY - (rect.top + getParentWindow(node).pageYOffset)) / rect.height, 0, 1),
|
||||
};
|
||||
};
|
||||
|
||||
export interface InteractiveProps {
|
||||
onMove: (interaction: Interaction) => void;
|
||||
onKey: (offset: Interaction) => void;
|
||||
children: InfernoNode[];
|
||||
style?: any;
|
||||
}
|
||||
|
||||
export class Interactive extends Component {
|
||||
containerRef: RefObject<HTMLDivElement>;
|
||||
props: InteractiveProps;
|
||||
|
||||
constructor(props: InteractiveProps) {
|
||||
super();
|
||||
this.props = props;
|
||||
this.containerRef = createRef();
|
||||
}
|
||||
|
||||
handleMoveStart = (event: MouseEvent) => {
|
||||
const el = this.containerRef?.current;
|
||||
if (!el) return;
|
||||
|
||||
// Prevent text selection
|
||||
event.preventDefault();
|
||||
el.focus();
|
||||
this.props.onMove(getRelativePosition(el, event));
|
||||
this.toggleDocumentEvents(true);
|
||||
};
|
||||
|
||||
handleMove = (event: MouseEvent) => {
|
||||
// Prevent text selection
|
||||
event.preventDefault();
|
||||
|
||||
// If user moves the pointer outside of the window or iframe bounds and release it there,
|
||||
// `mouseup`/`touchend` won't be fired. In order to stop the picker from following the cursor
|
||||
// after the user has moved the mouse/finger back to the document, we check `event.buttons`
|
||||
// and `event.touches`. It allows us to detect that the user is just moving his pointer
|
||||
// without pressing it down
|
||||
const isDown = event.buttons > 0;
|
||||
|
||||
if (isDown && this.containerRef?.current) {
|
||||
this.props.onMove(getRelativePosition(this.containerRef.current, event));
|
||||
} else {
|
||||
this.toggleDocumentEvents(false);
|
||||
}
|
||||
};
|
||||
|
||||
handleMoveEnd = () => {
|
||||
this.toggleDocumentEvents(false);
|
||||
};
|
||||
|
||||
handleKeyDown = (event: KeyboardEvent) => {
|
||||
const keyCode = event.which || event.keyCode;
|
||||
|
||||
// Ignore all keys except arrow ones
|
||||
if (keyCode < 37 || keyCode > 40) return;
|
||||
// Do not scroll page by arrow keys when document is focused on the element
|
||||
event.preventDefault();
|
||||
// Send relative offset to the parent component.
|
||||
// We use codes (37←, 38↑, 39→, 40↓) instead of keys ('ArrowRight', 'ArrowDown', etc)
|
||||
// to reduce the size of the library
|
||||
this.props.onKey({
|
||||
left: keyCode === 39 ? 0.05 : keyCode === 37 ? -0.05 : 0,
|
||||
top: keyCode === 40 ? 0.05 : keyCode === 38 ? -0.05 : 0,
|
||||
});
|
||||
};
|
||||
|
||||
toggleDocumentEvents(state?: boolean) {
|
||||
const el = this.containerRef?.current;
|
||||
const parentWindow = getParentWindow(el);
|
||||
|
||||
// Add or remove additional pointer event listeners
|
||||
const toggleEvent = state ? parentWindow.addEventListener : parentWindow.removeEventListener;
|
||||
toggleEvent('mousemove', this.handleMove);
|
||||
toggleEvent('mouseup', this.handleMoveEnd);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.toggleDocumentEvents(true);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.toggleDocumentEvents(false);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
{...this.props}
|
||||
style={this.props.style}
|
||||
ref={this.containerRef}
|
||||
onMouseDown={this.handleMoveStart}
|
||||
className="react-colorful__interactive"
|
||||
onKeyDown={this.handleKeyDown}
|
||||
tabIndex={0}
|
||||
role="slider"
|
||||
>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* MIT License
|
||||
* https://github.com/omgovich/react-colorful/
|
||||
*
|
||||
* Copyright (c) 2020 Vlad Shilov <omgovich@ya.ru>
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import { classes } from 'common/react';
|
||||
import { InfernoNode } from 'inferno';
|
||||
|
||||
interface PointerProps {
|
||||
className?: string;
|
||||
top?: number;
|
||||
left: number;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export const Pointer = ({ className, color, left, top = 0.5 }: PointerProps): InfernoNode => {
|
||||
const nodeClassName = classes(['react-colorful__pointer', className]);
|
||||
|
||||
const style = {
|
||||
top: `${top * 100}%`,
|
||||
left: `${left * 100}%`,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={nodeClassName} style={style}>
|
||||
<div className="react-colorful__pointer-fill" style={{ 'background-color': color }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -21,6 +21,7 @@ export { DraggableControl } from './DraggableControl';
|
||||
export { Dropdown } from './Dropdown';
|
||||
export { Flex } from './Flex';
|
||||
export { Grid } from './Grid';
|
||||
export { Interactive } from './Interactive';
|
||||
export { Image } from './Image';
|
||||
export { DmIcon } from './DmIcon';
|
||||
export { Icon } from './Icon';
|
||||
@@ -33,6 +34,7 @@ export { Modal } from './Modal';
|
||||
export { NanoMap } from './NanoMap';
|
||||
export { NoticeBox } from './NoticeBox';
|
||||
export { NumberInput } from './NumberInput';
|
||||
export { Pointer } from './Pointer';
|
||||
export { Popper } from './Popper';
|
||||
export { ProgressBar } from './ProgressBar';
|
||||
export { RestrictedInput } from './RestrictedInput';
|
||||
|
||||
@@ -0,0 +1,596 @@
|
||||
/* eslint-disable react/state-in-constructor */
|
||||
/**
|
||||
* @file
|
||||
* @copyright 2023 itsmeow
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Loader } from './common/Loader';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Autofocus, Box, Flex, Section, Stack, Pointer, NumberInput, Tooltip } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { clamp } from 'common/math';
|
||||
import { hexToHsva, HsvaColor, hsvaToHex, hsvaToHslString, hsvaToRgba, rgbaToHsva, validHex } from 'common/color';
|
||||
import { Interaction, Interactive } from 'tgui/components/Interactive';
|
||||
import { classes } from 'common/react';
|
||||
import { Component, FocusEvent, FormEvent, InfernoNode } from 'inferno';
|
||||
import { logger } from 'tgui/logging';
|
||||
import { InputButtons } from './common/InputButtons';
|
||||
|
||||
type ColorPickerData = {
|
||||
autofocus: boolean;
|
||||
buttons: string[];
|
||||
message: string;
|
||||
large_buttons: boolean;
|
||||
swapped_buttons: boolean;
|
||||
timeout: number;
|
||||
title: string;
|
||||
default_color: string;
|
||||
};
|
||||
|
||||
export const ColorPickerModal = (_, context) => {
|
||||
const { data } = useBackend<ColorPickerData>(context);
|
||||
const { timeout, message, title, autofocus, default_color = '#000000' } = data;
|
||||
let [selectedColor, setSelectedColor] = useLocalState<HsvaColor>(
|
||||
context,
|
||||
'color_picker_choice',
|
||||
hexToHsva(default_color)
|
||||
);
|
||||
|
||||
return (
|
||||
<Window height={400} title={title} width={600} theme="generic">
|
||||
{!!timeout && <Loader value={timeout} />}
|
||||
<Window.Content>
|
||||
<Stack fill vertical>
|
||||
{message && (
|
||||
<Stack.Item m={1}>
|
||||
<Section fill>
|
||||
<Box color="label" overflow="hidden">
|
||||
{message}
|
||||
</Box>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
)}
|
||||
<Stack.Item grow>
|
||||
<Section fill>
|
||||
{!!autofocus && <Autofocus />}
|
||||
<ColorSelector color={selectedColor} setColor={setSelectedColor} defaultColor={default_color} />
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<InputButtons input={hsvaToHex(selectedColor)} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
export const ColorSelector = (
|
||||
{ color, setColor, defaultColor }: { color: HsvaColor; setColor; defaultColor: string },
|
||||
context
|
||||
) => {
|
||||
const handleChange = (params: Partial<HsvaColor>) => {
|
||||
setColor((current: HsvaColor) => {
|
||||
return Object.assign({}, current, params);
|
||||
});
|
||||
};
|
||||
const rgb = hsvaToRgba(color);
|
||||
const hexColor = hsvaToHex(color);
|
||||
return (
|
||||
<Flex direction="row">
|
||||
<Flex.Item mr={2}>
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<div className="react-colorful">
|
||||
<SaturationValue hsva={color} onChange={handleChange} />
|
||||
<Hue hue={color.h} onChange={handleChange} className="react-colorful__last-control" />
|
||||
</div>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Box inline width="100px" height="20px" textAlign="center">
|
||||
Current
|
||||
</Box>
|
||||
<Box inline width="100px" height="20px" textAlign="center">
|
||||
Previous
|
||||
</Box>
|
||||
<br />
|
||||
<Tooltip content={hexColor} position="bottom">
|
||||
<Box inline width="100px" height="30px" backgroundColor={hexColor} />
|
||||
</Tooltip>
|
||||
<Tooltip content={defaultColor} position="bottom">
|
||||
<Box inline width="100px" height="30px" backgroundColor={defaultColor} />
|
||||
</Tooltip>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow fontSize="15px" lineHeight="24px">
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Box textColor="label">Hex:</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow height="24px">
|
||||
<HexColorInput
|
||||
fluid
|
||||
color={hsvaToHex(color).substring(1)}
|
||||
onChange={(value) => {
|
||||
logger.info(value);
|
||||
setColor(hexToHsva(value));
|
||||
}}
|
||||
prefixed
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item width="25px">
|
||||
<Box textColor="label">H:</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Hue hue={color.h} onChange={handleChange} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<TextSetter value={color.h} callback={(_, v) => handleChange({ h: v })} max={360} unit="°" />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item width="25px">
|
||||
<Box textColor="label">S:</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Saturation color={color} onChange={handleChange} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<TextSetter value={color.s} callback={(_, v) => handleChange({ s: v })} unit="%" />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item width="25px">
|
||||
<Box textColor="label">V:</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Value color={color} onChange={handleChange} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<TextSetter value={color.v} callback={(_, v) => handleChange({ v: v })} unit="%" />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item width="25px">
|
||||
<Box textColor="label">R:</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<RGBSlider color={color} onChange={handleChange} target="r" />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<TextSetter
|
||||
value={rgb.r}
|
||||
callback={(_, v) => {
|
||||
rgb.r = v;
|
||||
handleChange(rgbaToHsva(rgb));
|
||||
}}
|
||||
max={255}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item width="25px">
|
||||
<Box textColor="label">G:</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<RGBSlider color={color} onChange={handleChange} target="g" />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<TextSetter
|
||||
value={rgb.g}
|
||||
callback={(_, v) => {
|
||||
rgb.g = v;
|
||||
handleChange(rgbaToHsva(rgb));
|
||||
}}
|
||||
max={255}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item width="25px">
|
||||
<Box textColor="label">B:</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<RGBSlider color={color} onChange={handleChange} target="b" />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<TextSetter
|
||||
value={rgb.b}
|
||||
callback={(_, v) => {
|
||||
rgb.b = v;
|
||||
handleChange(rgbaToHsva(rgb));
|
||||
}}
|
||||
max={255}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const TextSetter = ({
|
||||
value,
|
||||
callback,
|
||||
min = 0,
|
||||
max = 100,
|
||||
unit,
|
||||
}: {
|
||||
value: number;
|
||||
callback: any;
|
||||
min?: number;
|
||||
max?: number;
|
||||
unit?: string;
|
||||
}) => {
|
||||
return (
|
||||
<NumberInput
|
||||
width="70px"
|
||||
value={Math.round(value)}
|
||||
step={1}
|
||||
minValue={min}
|
||||
maxValue={max}
|
||||
onChange={callback}
|
||||
unit={unit}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* MIT License
|
||||
* https://github.com/omgovich/react-colorful/
|
||||
*
|
||||
* Copyright (c) 2020 Vlad Shilov <omgovich@ya.ru>
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
interface HexColorInputProps extends Omit<ColorInputBaseProps, 'escape' | 'validate'> {
|
||||
/** Enables `#` prefix displaying */
|
||||
prefixed?: boolean;
|
||||
/** Allows `#rgba` and `#rrggbbaa` color formats */
|
||||
alpha?: boolean;
|
||||
}
|
||||
|
||||
/** Adds "#" symbol to the beginning of the string */
|
||||
const prefix = (value: string) => '#' + value;
|
||||
|
||||
export const HexColorInput = (props: HexColorInputProps): InfernoNode => {
|
||||
const { prefixed, alpha, color, fluid, onChange, ...rest } = props;
|
||||
|
||||
/** Escapes all non-hexadecimal characters including "#" */
|
||||
const escape = (value: string) => value.replace(/([^0-9A-F]+)/gi, '').substring(0, alpha ? 8 : 6);
|
||||
|
||||
/** Validates hexadecimal strings */
|
||||
const validate = (value: string) => validHex(value, alpha);
|
||||
|
||||
return (
|
||||
<ColorInput
|
||||
{...rest}
|
||||
fluid={fluid}
|
||||
color={color}
|
||||
onChange={onChange}
|
||||
escape={escape}
|
||||
format={prefixed ? prefix : undefined}
|
||||
validate={validate}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface ColorInputBaseProps {
|
||||
fluid?: boolean;
|
||||
color: string;
|
||||
onChange: (newColor: string) => void;
|
||||
/** Blocks typing invalid characters and limits string length */
|
||||
escape: (value: string) => string;
|
||||
/** Checks that value is valid color string */
|
||||
validate: (value: string) => boolean;
|
||||
/** Processes value before displaying it in the input */
|
||||
format?: (value: string) => string;
|
||||
}
|
||||
|
||||
export class ColorInput extends Component {
|
||||
props: ColorInputBaseProps;
|
||||
state: { localValue: string };
|
||||
|
||||
constructor(props: ColorInputBaseProps) {
|
||||
super();
|
||||
this.props = props;
|
||||
this.state = { localValue: this.props.escape(this.props.color) };
|
||||
}
|
||||
|
||||
// Trigger `onChange` handler only if the input value is a valid color
|
||||
handleInput = (e: FormEvent<HTMLInputElement>) => {
|
||||
const inputValue = this.props.escape(e.currentTarget.value);
|
||||
this.setState({ localValue: inputValue });
|
||||
};
|
||||
|
||||
// Take the color from props if the last typed color (in local state) is not valid
|
||||
handleBlur = (e: FocusEvent<HTMLInputElement>) => {
|
||||
if (e.currentTarget) {
|
||||
if (!this.props.validate(e.currentTarget.value)) {
|
||||
this.setState({ localValue: this.props.escape(this.props.color) }); // return to default;
|
||||
} else {
|
||||
this.props.onChange(this.props.escape ? this.props.escape(e.currentTarget.value) : e.currentTarget.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps, prevState): void {
|
||||
if (prevProps.color !== this.props.color) {
|
||||
// Update the local state when `color` property value is changed
|
||||
this.setState({ localValue: this.props.escape(this.props.color) });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Box className={classes(['Input', this.props.fluid && 'Input--fluid'])}>
|
||||
<div className="Input__baseline">.</div>
|
||||
<input
|
||||
className="Input__input"
|
||||
value={this.props.format ? this.props.format(this.state.localValue) : this.state.localValue}
|
||||
spellCheck="false" // the element should not be checked for spelling errors
|
||||
onInput={this.handleInput}
|
||||
onBlur={this.handleBlur}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const SaturationValue = ({ hsva, onChange }) => {
|
||||
const handleMove = (interaction: Interaction) => {
|
||||
onChange({
|
||||
s: interaction.left * 100,
|
||||
v: 100 - interaction.top * 100,
|
||||
});
|
||||
};
|
||||
|
||||
const handleKey = (offset: Interaction) => {
|
||||
// Saturation and brightness always fit into [0, 100] range
|
||||
onChange({
|
||||
s: clamp(hsva.s + offset.left * 100, 0, 100),
|
||||
v: clamp(hsva.v - offset.top * 100, 0, 100),
|
||||
});
|
||||
};
|
||||
|
||||
const containerStyle = {
|
||||
'background-color': `${hsvaToHslString({ h: hsva.h, s: 100, v: 100, a: 1 })} !important`,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="react-colorful__saturation_value" style={containerStyle}>
|
||||
<Interactive
|
||||
onMove={handleMove}
|
||||
onKey={handleKey}
|
||||
aria-label="Color"
|
||||
aria-valuetext={`Saturation ${Math.round(hsva.s)}%, Brightness ${Math.round(hsva.v)}%`}
|
||||
>
|
||||
<Pointer
|
||||
className="react-colorful__saturation_value-pointer"
|
||||
top={1 - hsva.v / 100}
|
||||
left={hsva.s / 100}
|
||||
color={hsvaToHslString(hsva)}
|
||||
/>
|
||||
</Interactive>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Hue = ({
|
||||
className,
|
||||
hue,
|
||||
onChange,
|
||||
}: {
|
||||
className?: string;
|
||||
hue: number;
|
||||
onChange: (newHue: { h: number }) => void;
|
||||
}) => {
|
||||
const handleMove = (interaction: Interaction) => {
|
||||
onChange({ h: 360 * interaction.left });
|
||||
};
|
||||
|
||||
const handleKey = (offset: Interaction) => {
|
||||
// Hue measured in degrees of the color circle ranging from 0 to 360
|
||||
onChange({
|
||||
h: clamp(hue + offset.left * 360, 0, 360),
|
||||
});
|
||||
};
|
||||
|
||||
const nodeClassName = classes(['react-colorful__hue', className]);
|
||||
|
||||
return (
|
||||
<div className={nodeClassName}>
|
||||
<Interactive
|
||||
onMove={handleMove}
|
||||
onKey={handleKey}
|
||||
aria-label="Hue"
|
||||
aria-valuenow={Math.round(hue)}
|
||||
aria-valuemax="360"
|
||||
aria-valuemin="0"
|
||||
>
|
||||
<Pointer
|
||||
className="react-colorful__hue-pointer"
|
||||
left={hue / 360}
|
||||
color={hsvaToHslString({ h: hue, s: 100, v: 100, a: 1 })}
|
||||
/>
|
||||
</Interactive>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Saturation = ({
|
||||
className,
|
||||
color,
|
||||
onChange,
|
||||
}: {
|
||||
className?: string;
|
||||
color: HsvaColor;
|
||||
onChange: (newSaturation: { s: number }) => void;
|
||||
}) => {
|
||||
const handleMove = (interaction: Interaction) => {
|
||||
onChange({ s: 100 * interaction.left });
|
||||
};
|
||||
|
||||
const handleKey = (offset: Interaction) => {
|
||||
// Hue measured in degrees of the color circle ranging from 0 to 100
|
||||
onChange({
|
||||
s: clamp(color.s + offset.left * 100, 0, 100),
|
||||
});
|
||||
};
|
||||
|
||||
const nodeClassName = classes(['react-colorful__saturation', className]);
|
||||
|
||||
return (
|
||||
<div className={nodeClassName}>
|
||||
<Interactive
|
||||
style={{
|
||||
'background': `linear-gradient(to right, ${hsvaToHslString({
|
||||
h: color.h,
|
||||
s: 0,
|
||||
v: color.v,
|
||||
a: 1,
|
||||
})}, ${hsvaToHslString({ h: color.h, s: 100, v: color.v, a: 1 })})`,
|
||||
}}
|
||||
onMove={handleMove}
|
||||
onKey={handleKey}
|
||||
aria-label="Saturation"
|
||||
aria-valuenow={Math.round(color.s)}
|
||||
aria-valuemax="100"
|
||||
aria-valuemin="0"
|
||||
>
|
||||
<Pointer
|
||||
className="react-colorful__saturation-pointer"
|
||||
left={color.s / 100}
|
||||
color={hsvaToHslString({ h: color.h, s: color.s, v: color.v, a: 1 })}
|
||||
/>
|
||||
</Interactive>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Value = ({
|
||||
className,
|
||||
color,
|
||||
onChange,
|
||||
}: {
|
||||
className?: string;
|
||||
color: HsvaColor;
|
||||
onChange: (newValue: { v: number }) => void;
|
||||
}) => {
|
||||
const handleMove = (interaction: Interaction) => {
|
||||
onChange({ v: 100 * interaction.left });
|
||||
};
|
||||
|
||||
const handleKey = (offset: Interaction) => {
|
||||
onChange({
|
||||
v: clamp(color.v + offset.left * 100, 0, 100),
|
||||
});
|
||||
};
|
||||
|
||||
const nodeClassName = classes(['react-colorful__value', className]);
|
||||
|
||||
return (
|
||||
<div className={nodeClassName}>
|
||||
<Interactive
|
||||
style={{
|
||||
'background': `linear-gradient(to right, ${hsvaToHslString({
|
||||
h: color.h,
|
||||
s: color.s,
|
||||
v: 0,
|
||||
a: 1,
|
||||
})}, ${hsvaToHslString({ h: color.h, s: color.s, v: 100, a: 1 })})`,
|
||||
}}
|
||||
onMove={handleMove}
|
||||
onKey={handleKey}
|
||||
aria-label="Value"
|
||||
aria-valuenow={Math.round(color.s)}
|
||||
aria-valuemax="100"
|
||||
aria-valuemin="0"
|
||||
>
|
||||
<Pointer
|
||||
className="react-colorful__value-pointer"
|
||||
left={color.v / 100}
|
||||
color={hsvaToHslString({ h: color.h, s: color.s, v: color.v, a: 1 })}
|
||||
/>
|
||||
</Interactive>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const RGBSlider = ({
|
||||
className,
|
||||
color,
|
||||
onChange,
|
||||
target,
|
||||
}: {
|
||||
className?: string;
|
||||
color: HsvaColor;
|
||||
onChange: (newValue: HsvaColor) => void;
|
||||
target: string;
|
||||
}) => {
|
||||
const rgb = hsvaToRgba(color);
|
||||
|
||||
const setNewTarget = (value: number) => {
|
||||
rgb[target] = value;
|
||||
onChange(rgbaToHsva(rgb));
|
||||
};
|
||||
|
||||
const handleMove = (interaction: Interaction) => {
|
||||
setNewTarget(255 * interaction.left);
|
||||
};
|
||||
|
||||
const handleKey = (offset: Interaction) => {
|
||||
setNewTarget(clamp(rgb[target] + offset.left * 255, 0, 255));
|
||||
};
|
||||
|
||||
const nodeClassName = classes([`react-colorful__${target}`, className]);
|
||||
|
||||
let selected =
|
||||
target === 'r'
|
||||
? `rgb(${Math.round(rgb.r)},0,0)`
|
||||
: target === 'g'
|
||||
? `rgb(0,${Math.round(rgb.g)},0)`
|
||||
: `rgb(0,0,${Math.round(rgb.b)})`;
|
||||
|
||||
return (
|
||||
<div className={nodeClassName}>
|
||||
<Interactive
|
||||
onMove={handleMove}
|
||||
onKey={handleKey}
|
||||
aria-valuenow={rgb[target]}
|
||||
aria-valuemax="100"
|
||||
aria-valuemin="0"
|
||||
>
|
||||
<Pointer className={`react-colorful__${target}-pointer`} left={rgb[target] / 255} color={selected} />
|
||||
</Interactive>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* MIT License
|
||||
* https://github.com/omgovich/react-colorful/
|
||||
*
|
||||
* Copyright (c) 2020 Vlad Shilov <omgovich@ya.ru>
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
@use '../colors.scss';
|
||||
@use '../base.scss';
|
||||
|
||||
.react-colorful {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.react-colorful__saturation_value {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
border-color: transparent; /* Fixes https://github.com/omgovich/react-colorful/issues/139 */
|
||||
border-bottom: 12px solid #000;
|
||||
border-radius: 8px 8px 0 0;
|
||||
background-image: linear-gradient(to top, rgba(0, 0, 0, 255), rgba(0, 0, 0, 0)),
|
||||
linear-gradient(to right, rgba(255, 255, 255, 255), rgba(255, 255, 255, 0));
|
||||
}
|
||||
|
||||
.react-colorful__pointer-fill,
|
||||
.react-colorful__alpha-gradient {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
/* Improve elements rendering on light backgrounds */
|
||||
.react-colorful__alpha-gradient,
|
||||
.react-colorful__saturation_value {
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.react-colorful__hue,
|
||||
.react-colorful__r,
|
||||
.react-colorful__g,
|
||||
.react-colorful__b,
|
||||
.react-colorful__alpha,
|
||||
.react-colorful__saturation,
|
||||
.react-colorful__value {
|
||||
position: relative;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.react-colorful__hue {
|
||||
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
|
||||
}
|
||||
|
||||
.react-colorful__r {
|
||||
background: linear-gradient(to right, #000, #f00);
|
||||
}
|
||||
|
||||
.react-colorful__g {
|
||||
background: linear-gradient(to right, #000, #0f0);
|
||||
}
|
||||
|
||||
.react-colorful__b {
|
||||
background: linear-gradient(to right, #000, #00f);
|
||||
}
|
||||
|
||||
/* Round bottom corners of the last element: `Hue` for `ColorPicker` or `Alpha` for `AlphaColorPicker` */
|
||||
.react-colorful__last-control {
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
.react-colorful__interactive {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: inherit;
|
||||
outline: none;
|
||||
/* Don't trigger the default scrolling behavior when the event is originating from this element */
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.react-colorful__pointer {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #cfcfcf;
|
||||
border: 2px solid #cfcfcf;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.react-colorful__interactive:focus .react-colorful__pointer {
|
||||
transform: translate(-50%, -50%) scale(1.1);
|
||||
background-color: #fff;
|
||||
border-color: #fff;
|
||||
}
|
||||
|
||||
/* Chessboard-like pattern for alpha related elements */
|
||||
.react-colorful__alpha,
|
||||
.react-colorful__alpha-pointer {
|
||||
background-color: #fff;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill-opacity=".05"><rect x="8" width="8" height="8"/><rect y="8" width="8" height="8"/></svg>');
|
||||
}
|
||||
|
||||
.react-colorful__saturation-pointer,
|
||||
.react-colorful__value-pointer,
|
||||
.react-colorful__hue-pointer,
|
||||
.react-colorful__r-pointer,
|
||||
.react-colorful__g-pointer,
|
||||
.react-colorful__b-pointer {
|
||||
z-index: 1;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* Display the saturation value pointer over the hue one */
|
||||
.react-colorful__saturation_value-pointer {
|
||||
z-index: 3;
|
||||
}
|
||||
@@ -49,6 +49,7 @@
|
||||
@include meta.load-css('./interfaces/AlertModal.scss');
|
||||
@include meta.load-css('./interfaces/BrigCells.scss');
|
||||
@include meta.load-css('./interfaces/CameraConsole.scss');
|
||||
@include meta.load-css('./interfaces/ColorPicker.scss');
|
||||
@include meta.load-css('./interfaces/Contractor.scss');
|
||||
@include meta.load-css('./interfaces/CrewMonitor.scss');
|
||||
@include meta.load-css('./interfaces/ExosuitFabricator.scss');
|
||||
|
||||
+122
-122
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+67
-59
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user