diff --git a/.vscode/settings.json b/.vscode/settings.json index 8e110ef9bd..6f42af3c40 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "eslint.nodePath": "./tgui/.yarn/sdks", "eslint.workingDirectories": ["./tgui"], - "prettier.prettierPath": "./tgui/.yarn/sdks/prettier/index.js", + "prettier.prettierPath": "./tgui/.yarn/sdks/prettier/index.cjs", "typescript.tsdk": "./tgui/.yarn/sdks/typescript/lib", "typescript.enablePromptUseWorkspaceTsdk": true, "search.exclude": { @@ -11,6 +11,9 @@ "workbench.editorAssociations": { "*.dmi": "imagePreview.previewEditor" }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, "files.eol": "\n", "files.encoding": "utf8", "files.insertFinalNewline": true, diff --git a/code/modules/client/preference_setup/loadout/loadout_accessories_vr.dm b/code/modules/client/preference_setup/loadout/loadout_accessories_vr.dm index 8fa00e9e41..235267a044 100644 --- a/code/modules/client/preference_setup/loadout/loadout_accessories_vr.dm +++ b/code/modules/client/preference_setup/loadout/loadout_accessories_vr.dm @@ -155,4 +155,28 @@ Talon pin /datum/gear/accessory/antediluvian/loin display_name = "antediluvian loincloth" - path = /obj/item/clothing/accessory/antediluvian/loincloth \ No newline at end of file + path = /obj/item/clothing/accessory/antediluvian/loincloth + +//Replikant accessories + +/datum/gear/accessory/sleekpatch + display_name = "sleek uniform patch" + path = /obj/item/clothing/accessory/sleekpatch + +/datum/gear/accessory/poncho/roles/cloak/custom/gestaltjacket + display_name = "sleek uniform jacket" + path = /obj/item/clothing/accessory/poncho/roles/cloak/custom/gestaltjacket + +/datum/gear/accessory/replika + display_name = "replikant vest selection" + path = /obj/item/clothing/accessory/replika + +/datum/gear/accessory/replika/New() + ..() + var/list/replika_vests = list( + "controller replikant chestplate" = /obj/item/clothing/accessory/replika/klbr, + "combat-engineer replikant chestplate" = /obj/item/clothing/accessory/replika/lstr, + "security-controller replikant chestplate" = /obj/item/clothing/accessory/replika/stcr, + "security-technician replikant chestplate" = /obj/item/clothing/accessory/replika/star + ) + gear_tweaks += new/datum/gear_tweak/path(replika_vests) diff --git a/code/modules/client/preference_setup/loadout/loadout_head.dm b/code/modules/client/preference_setup/loadout/loadout_head.dm index 435ed5ff30..7716e44d74 100644 --- a/code/modules/client/preference_setup/loadout/loadout_head.dm +++ b/code/modules/client/preference_setup/loadout/loadout_head.dm @@ -297,7 +297,8 @@ "engineering"=/obj/item/clothing/head/welding/engie, "fancy"=/obj/item/clothing/head/welding/fancy, "demonic"=/obj/item/clothing/head/welding/demon, - "knightly"=/obj/item/clothing/head/welding/knight + "knightly"=/obj/item/clothing/head/welding/knight, + "replikant"=/obj/item/clothing/head/welding/arar ) gear_tweaks += new/datum/gear_tweak/path(sortAssoc(selector_uniforms)) diff --git a/code/modules/client/preference_setup/loadout/loadout_head_vr.dm b/code/modules/client/preference_setup/loadout/loadout_head_vr.dm index 52d81e806d..48015f229e 100644 --- a/code/modules/client/preference_setup/loadout/loadout_head_vr.dm +++ b/code/modules/client/preference_setup/loadout/loadout_head_vr.dm @@ -92,3 +92,9 @@ Talon hats /datum/gear/head/tiny_tophat display_name = "tiny tophat" path = /obj/item/clothing/head/tinytophat + +//Replikant hat + +/datum/gear/head/eulrhat + display_name = "Sleek side cap" + path = /obj/item/clothing/head/eulrhat diff --git a/code/modules/client/preference_setup/loadout/loadout_uni_selector.dm b/code/modules/client/preference_setup/loadout/loadout_uni_selector.dm index f02baa6051..db4f0c6fdb 100644 --- a/code/modules/client/preference_setup/loadout/loadout_uni_selector.dm +++ b/code/modules/client/preference_setup/loadout/loadout_uni_selector.dm @@ -592,4 +592,44 @@ "TG&C jumpsuit"=/obj/item/clothing/under/rank/neo_robo, "TG&C jumpskirt"=/obj/item/clothing/under/rank/neo_robo_skirt ) - gear_tweaks += new/datum/gear_tweak/path(sortAssoc(selector_uniforms)) \ No newline at end of file + gear_tweaks += new/datum/gear_tweak/path(sortAssoc(selector_uniforms)) + +//Replikant & Signalis-themed human-wear + +/datum/gear/uniform/replikant_selector + display_name = "Replikant Uniform Selection" + description = "Several variants of bodysuit designed for Second Generation Biosynthetics." + path = /obj/item/clothing/under/replika/arar + sort_category = "Uniforms" + cost = 1 + +/datum/gear/uniform/replikant_selector/New() + ..() + var/list/selector_uniforms = list( + "Macaw"=/obj/item/clothing/under/replika/arar, + "Magpie"=/obj/item/clothing/under/replika/lstr, + "Falcon"=/obj/item/clothing/under/replika/fklr, + "Owl"=/obj/item/clothing/under/replika/eulr, + "Hummingbird"=/obj/item/clothing/under/replika/klbr, + "Stork/Starling"=/obj/item/clothing/under/replika/stcr, + "Eagle"=/obj/item/clothing/under/replika/adlr, + "Magpie, Alternate"=/obj/item/clothing/under/replika/lstr_alt + ) + gear_tweaks += new/datum/gear_tweak/path(sortAssoc(selector_uniforms)) + +/datum/gear/uniform/gestalt_selector + display_name = "Sleek Uniform Selection" + description = "Multiple variants of single-stripe pattern uniforms. Best worn under their accompanying jacket." + path = /obj/item/clothing/under/gestalt + sort_category = "Uniforms" + cost = 1 + +/datum/gear/uniform/gestalt_selector/New() + ..() + var/list/selector_uniforms = list( + "Sleek, standard"=/obj/item/clothing/under/gestalt/sleek, + "Sleek, skirt"=/obj/item/clothing/under/gestalt/sleek_skirt, + "Sleek, feminine"=/obj/item/clothing/under/gestalt/sleek_fem, + "Sleek, sleeveless"=/obj/item/clothing/under/gestalt/sleeveless + ) + gear_tweaks += new/datum/gear_tweak/path(sortAssoc(selector_uniforms)) diff --git a/code/modules/client/preference_setup/loadout/loadout_uniform.dm b/code/modules/client/preference_setup/loadout/loadout_uniform.dm index b6bad99996..f5c9aab68c 100644 --- a/code/modules/client/preference_setup/loadout/loadout_uniform.dm +++ b/code/modules/client/preference_setup/loadout/loadout_uniform.dm @@ -516,6 +516,10 @@ display_name = "high-waisted trousers" path = /obj/item/clothing/under/dress/hightrousers +/datum/gear/uniform/vampirehunter + display_name = "18th century outfit" + path = /obj/item/clothing/under/vampirehunter + /* * 80s */ diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm index 724b77a9af..c2509fbb88 100644 --- a/code/modules/clothing/head/misc_special.dm +++ b/code/modules/clothing/head/misc_special.dm @@ -103,6 +103,22 @@ slot_r_hand_str = "engiewelding", ) +//Replikant Welding mask + +/obj/item/clothing/head/welding/arar + name = "replikant welding helmet" + desc = "A protective welding mask designed for repair-technician biosynthetic crew, the visor slits are particularly difficult to see out of." + icon = 'icons/inventory/head/item_vr.dmi' + icon_override = 'icons/inventory/head/mob_vr.dmi' + icon_state = "ararwelding" + item_state_slots = list( + SLOT_ID_LEFT_HAND = "ararwelding", + SLOT_ID_RIGHT_HAND = "ararwelding", + ) + + + + /* * Cakehat */ @@ -319,4 +335,4 @@ w_class = 2 body_parts_covered = HEAD attack_verb = list("warned", "cautioned", "smashed") - armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) \ No newline at end of file + armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) diff --git a/code/modules/clothing/head/misc_vr.dm b/code/modules/clothing/head/misc_vr.dm index 6879717a69..cceb01a66a 100644 --- a/code/modules/clothing/head/misc_vr.dm +++ b/code/modules/clothing/head/misc_vr.dm @@ -171,4 +171,13 @@ desc = "A tophat that is far too small to properly sit on someone's head!" icon = 'icons/inventory/head/item_vr.dmi' default_worn_icon = 'icons/inventory/head/mob_vr.dmi' - icon_state = "tiny_tophat" \ No newline at end of file + icon_state = "tiny_tophat" + +//Replikant Hat + +/obj/item/clothing/head/eulrhat + name = "sleek side cap" + desc = "A simple wedge cap with red accents, popular with biosynthetic personnel." + icon = 'icons/inventory/head/item_vr.dmi' + icon_override = 'icons/inventory/head/mob_vr.dmi' + icon_state = "eulrhat" diff --git a/code/modules/clothing/under/accessories/accessory_vr.dm b/code/modules/clothing/under/accessories/accessory_vr.dm index fec423c436..d70e7c0df6 100644 --- a/code/modules/clothing/under/accessories/accessory_vr.dm +++ b/code/modules/clothing/under/accessories/accessory_vr.dm @@ -990,3 +990,17 @@ desc = "A cut down jacket that looks like it's light enough to wear on top of some other clothes. This one's a sort of olive-drab kind of colour." icon_state = "cropjacket_drab" item_state = "cropjacket_drab" + +//Replikant patch & jacket + +/obj/item/clothing/accessory/sleekpatch + name = "sleek uniform patch" + desc = "A somewhat old-fashioned embroidered patch of Nanotrasen's logo." + icon_state = "sleekpatch" + item_state = "sleekpatch" + +/obj/item/clothing/accessory/poncho/roles/cloak/custom/gestaltjacket + name = "sleek uniform jacket" + desc = "Barely more than a pair of long stirrup sleeves joined by a turtleneck. Has decorative red accents." + icon_state = "gestaltjacket" + item_state = "gestaltjacket" diff --git a/code/modules/clothing/under/accessories/clothing.dm b/code/modules/clothing/under/accessories/clothing.dm index 877c7b2d4b..10f72aa85a 100644 --- a/code/modules/clothing/under/accessories/clothing.dm +++ b/code/modules/clothing/under/accessories/clothing.dm @@ -533,3 +533,39 @@ /obj/item/clothing/accessory/cowboy_vest/grey name = "grey cowboy vest" icon_state = "cowboyvest_grey" + +//Replikant Vests + +/obj/item/clothing/accessory/replika + name = "generic" + desc = "generic" + icon = 'icons/inventory/accessory/item.dmi' + icon_state = "klbr" + icon_override = 'icons/inventory/accessory/mob.dmi' + item_state_slots = list(SLOT_ID_RIGHT_HAND = "armor", SLOT_ID_LEFT_HAND = "armor") + allowed = list(/obj/item/weapon/gun,/obj/item/weapon/reagent_containers/spray/pepper,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs,/obj/item/device/flashlight/maglight,/obj/item/clothing/head/helmet) + slot_flags = SLOT_OCLOTHING | SLOT_TIE + body_parts_covered = UPPER_TORSO|ARMS + siemens_coefficient = 0.9 + w_class = ITEMSIZE_NORMAL + slot = ACCESSORY_SLOT_OVER + +/obj/item/clothing/accessory/replika/klbr + name = "controller replikant chestplate" + desc = "A sloped titanium-composite chest plate fitted for use by 2nd generation biosynthetics. The right shoulder has been painted an imposing shade of red." + icon_state = "klbr" + +/obj/item/clothing/accessory/replika/lstr + name = "combat-engineer replikant chestplate" + desc = "A sloped titanium-composite chest plate fitted for use by 2nd generation biosynthetics. This plain-white version is a staple of biosynths assinged to combat-engineering duties." + icon_state = "lstr" + +/obj/item/clothing/accessory/replika/stcr + name = "security-controller replikant chestplate" + desc = "A sloped titanium-composite chest plate fitted for use by 2nd generation biosynthetics. This version sports multiple red adjustable straps and a lack of shoulder pads." + icon_state = "stcr" + +/obj/item/clothing/accessory/replika/star + name = "security-technician replikant chestplate" + desc = "A sloped titanium-composite chest plate with a matte black finish, fitted for use by 2nd generation biosynthetics. Comes with red adjustable straps." + icon_state = "star" diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index bcea7431f6..f135683178 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -1216,6 +1216,11 @@ desc = "An orange cohesion suit with yellow hazard stripes intended to assist Prometheans in maintaining their form and prevent direct skin exposure." icon_state = "cohesionsuit_hazard" +/obj/item/clothing/under/vampirehunter + name = "18th century outfit" + desc = "A flashy, if rather stiff set of ancient-styled slacks and tabard. The unyielding nature of the clothes often make one walk stiffly, but with divine purpose." + icon_state = "belmont" + //Ranger uniforms //On-mob sprites go in icons\mob\uniform.dmi with the format "white_ranger_uniform_s" - with 'white' replaced with green, cyan, etc... of course! Note the _s - this is not optional. //Item sprites go in icons\obj\clothing\ranger.dmi with the format "white_ranger_uniform" diff --git a/code/modules/clothing/under/miscellaneous_vr.dm b/code/modules/clothing/under/miscellaneous_vr.dm index 9d4633bcf8..a43a2676a7 100644 --- a/code/modules/clothing/under/miscellaneous_vr.dm +++ b/code/modules/clothing/under/miscellaneous_vr.dm @@ -616,3 +616,115 @@ desc = "A fancy gown for those who like to show leg. Perfect for recoloring!" default_worn_icon = 'icons/inventory/uniform/mob_vr.dmi' icon_state = "cswoopdress" + +//Replikant uniforms + +/obj/item/clothing/under/replika + name = "generic" + desc = "generic" + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon = 'icons/inventory/uniform/item_vr.dmi' + default_worn_icon = 'icons/inventory/uniform/mob_vr.dmi' + icon_state = "arar" + item_state = "arar" + rolled_sleeves = -1 + rolled_down = -1 + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + +/obj/item/clothing/under/replika/arar + name = "repair-worker replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of the engineering variety. Comes with multiple interfacing ports, arm protectors, and a conspicuous lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "arar" + item_state = "arar" + + +/obj/item/clothing/under/replika/lstr + name = "land-survey replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of the exploration variety. Comes with several interfacing ports and a conspicuous lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "lstr" + item_state = "lstr" + +/obj/item/clothing/under/replika/fklr + name = "command replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of the command variety. Comes with interfacing ports, an air of formality, and a conspicuous lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "fklr" + item_state = "fklr" + +/obj/item/clothing/under/replika/eulr + name = "general-purpose replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of multipurpose variety. Comes with default interfacing ports and a conspicuous lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "eulr" + item_state = "eulr" + +/obj/item/clothing/under/replika/klbr + name = "controller replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of the controller variety. Comes with several interfacing ports and a conspicuous lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "klbr" + item_state = "klbr" + +/obj/item/clothing/under/replika/stcr + name = "security-technician replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of the security variety. Comes with multiple interfacing ports and a conspicuous lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "stcr" + item_state = "stcr" + +/obj/item/clothing/under/replika/adlr + name = "administration replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of the administrative variety. Comes with several interfacing ports and a conspicuous lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "adlr" + item_state = "adlr" + +/obj/item/clothing/under/replika/lstr_alt + name = "combat-engineer replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of the exploration variety. Comes with extra interfacing ports, white armpads, and a familiar lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "lstr_alt" + item_state = "lstr_alt" + +//Signalis-themed human-wear + +/obj/item/clothing/under/gestalt + name = "generic" + desc = "generic" + icon = 'icons/inventory/uniform/item_vr.dmi' + default_worn_icon = 'icons/inventory/uniform/mob_vr.dmi' + icon_state = "gestalt_skirt" + item_state = "gestalt_skirt" + rolled_sleeves = -1 + rolled_down = -1 + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS|LEGS + +/obj/item/clothing/under/gestalt/sleek_skirt + name = "sleek crew skirt" + desc = "A tight-fitting black uniform with a narrow skirt and striking crimson trim." + icon_state = "gestalt_skirt" + item_state = "gestalt_skirt" + + +/obj/item/clothing/under/gestalt/sleek + name = "sleek crew uniform" + desc = "A tight-fitting black uniform with striking crimson trim." + icon_state = "gestalt" + item_state = "gestalt" + + +/obj/item/clothing/under/gestalt/sleek_fem + name = "sleek female crew uniform" + desc = "A tight-fitting black uniform with striking crimson trim." + icon_state = "gestalt_fem" + item_state = "gestalt_fem" + + +/obj/item/clothing/under/gestalt/sleeveless + name = "sleeveless sleek crew uniform" + desc = "A tight-fitting, sleeveless single-piece black uniform with striking crimson trim." + icon_state = "gestalt_sleeveless" + item_state = "gestalt_sleeveless" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS diff --git a/code/modules/mob/new_player/sprite_accessories_ear_vr.dm b/code/modules/mob/new_player/sprite_accessories_ear_vr.dm index 7802db8341..de137460cb 100644 --- a/code/modules/mob/new_player/sprite_accessories_ear_vr.dm +++ b/code/modules/mob/new_player/sprite_accessories_ear_vr.dm @@ -456,6 +456,14 @@ color_blend_mode = ICON_MULTIPLY extra_overlay = "vulp_jackal-inner" +/datum/sprite_accessory/ears/fox + name = "fox ears" + desc = "" + icon_state = "fox" + do_colouration = 1 + color_blend_mode = ICON_MULTIPLY + extra_overlay = "fox-inner" + /datum/sprite_accessory/ears/bunny_floppy name = "floopy bunny ears (colorable)" desc = "" @@ -1064,4 +1072,4 @@ icon_state = "triceratops_frill" extra_overlay = "triceratops_frill_spikes" do_colouration = 1 - color_blend_mode = ICON_MULTIPLY \ No newline at end of file + color_blend_mode = ICON_MULTIPLY diff --git a/code/modules/mob/new_player/sprite_accessories_extra_vr.dm b/code/modules/mob/new_player/sprite_accessories_extra_vr.dm index f6ca7340be..1c5be98e82 100644 --- a/code/modules/mob/new_player/sprite_accessories_extra_vr.dm +++ b/code/modules/mob/new_player/sprite_accessories_extra_vr.dm @@ -1139,6 +1139,50 @@ color_blend_mode = ICON_MULTIPLY body_parts = list(BP_TORSO) +//Replikant-specific markings + +/datum/sprite_accessory/marking/replikant/replika_r_thigh + name = "Replikant Stripe - Right Thigh" + icon = 'icons/mob/human_races/markings_vr.dmi' + icon_state = "replika" + color_blend_mode = ICON_MULTIPLY + body_parts = list(BP_R_LEG) + +/datum/sprite_accessory/marking/replikant/replika_r_knee + name = "Replikant Stripe - Right Knee" + icon = 'icons/mob/human_races/markings_vr.dmi' + icon_state = "replika" + color_blend_mode = ICON_MULTIPLY + body_parts = list(BP_R_FOOT) + +/datum/sprite_accessory/marking/replikant/replika_l_thigh + name = "Replikant Stripe - Left Thigh" + icon = 'icons/mob/human_races/markings_vr.dmi' + icon_state = "replika" + color_blend_mode = ICON_MULTIPLY + body_parts = list(BP_L_LEG) + +/datum/sprite_accessory/marking/replikant/replika_l_knee + name = "Replikant Stripe - Left Knee" + icon = 'icons/mob/human_races/markings_vr.dmi' + icon_state = "replika" + color_blend_mode = ICON_MULTIPLY + body_parts = list(BP_L_FOOT) + +/datum/sprite_accessory/marking/replikant/replika_panels_body + name = "Replikant Paneling - SynthFlesh (body)" + icon = 'icons/mob/human_races/markings_vr.dmi' + icon_state = "replikao" + color_blend_mode = ICON_MULTIPLY + body_parts = list(BP_TORSO) + +/datum/sprite_accessory/marking/replikant/replika_panels_groin + name = "Replikant Paneling - SynthFlesh (groin)" + icon = 'icons/mob/human_races/markings_vr.dmi' + icon_state = "replika" + color_blend_mode = ICON_MULTIPLY + body_parts = list(BP_GROIN) + //Digitigrade markings /datum/sprite_accessory/marking/digi icon = 'icons/mob/human_races/markings_digi.dmi' diff --git a/code/modules/organs/robolimbs.dm b/code/modules/organs/robolimbs.dm index ff8d1dd5c7..405cb39718 100644 --- a/code/modules/organs/robolimbs.dm +++ b/code/modules/organs/robolimbs.dm @@ -494,6 +494,25 @@ var/const/standard_monitor_styles = "blank=ipc_blank;\ species_alternates = list(SPECIES_HUMAN = "Morgan Trading Co") suggested_species = SPECIES_TESHARI +/datum/robolimb/replika + company = "Replikant" + desc = "An advanced biomechanical prosthetic with pegs for feet." + icon = 'icons/mob/human_races/cyberlimbs/replikant/replikant.dmi' + lifelike = 1 + unavailable_to_build = 1 + modular_bodyparts = MODULAR_BODYPART_PROSTHETIC + parts = list(BP_L_LEG, BP_R_LEG, BP_L_FOOT, BP_R_FOOT) + +/datum/robolimb/replika2 + company = "Replikant - 2nd Gen" + desc = "Modern, second-generation biomechanical prosthetics with pegs for feet." + icon = 'icons/mob/human_races/cyberlimbs/replikant/replikant2.dmi' + lifelike = 1 + unavailable_to_build = 1 + modular_bodyparts = MODULAR_BODYPART_PROSTHETIC + parts = list(BP_L_LEG, BP_R_LEG, BP_L_FOOT, BP_R_FOOT) + + /obj/item/weapon/disk/limb/New(var/newloc) ..() if(company) diff --git a/icons/inventory/accessory/item.dmi b/icons/inventory/accessory/item.dmi index 1c60ccfee6..2fc09f9efb 100644 Binary files a/icons/inventory/accessory/item.dmi and b/icons/inventory/accessory/item.dmi differ diff --git a/icons/inventory/accessory/mob.dmi b/icons/inventory/accessory/mob.dmi index 8fb324c931..dae029fcde 100644 Binary files a/icons/inventory/accessory/mob.dmi and b/icons/inventory/accessory/mob.dmi differ diff --git a/icons/inventory/head/item_vr.dmi b/icons/inventory/head/item_vr.dmi index 216a518951..813586c308 100644 Binary files a/icons/inventory/head/item_vr.dmi and b/icons/inventory/head/item_vr.dmi differ diff --git a/icons/inventory/head/mob_vr.dmi b/icons/inventory/head/mob_vr.dmi index b182d2c1c1..71f9907513 100644 Binary files a/icons/inventory/head/mob_vr.dmi and b/icons/inventory/head/mob_vr.dmi differ diff --git a/icons/inventory/uniform/item.dmi b/icons/inventory/uniform/item.dmi index c059c0f27d..9e143e828c 100644 Binary files a/icons/inventory/uniform/item.dmi and b/icons/inventory/uniform/item.dmi differ diff --git a/icons/inventory/uniform/item_vr.dmi b/icons/inventory/uniform/item_vr.dmi index 39f4eece02..a5d3708c0a 100644 Binary files a/icons/inventory/uniform/item_vr.dmi and b/icons/inventory/uniform/item_vr.dmi differ diff --git a/icons/inventory/uniform/mob.dmi b/icons/inventory/uniform/mob.dmi index 0b39bba6d2..17c2bde39f 100644 Binary files a/icons/inventory/uniform/mob.dmi and b/icons/inventory/uniform/mob.dmi differ diff --git a/icons/inventory/uniform/mob_vr.dmi b/icons/inventory/uniform/mob_vr.dmi index 9e1fc59a1c..25b58456e1 100644 Binary files a/icons/inventory/uniform/mob_vr.dmi and b/icons/inventory/uniform/mob_vr.dmi differ diff --git a/icons/mob/human_face_m.dmi b/icons/mob/human_face_m.dmi index 7986fb844f..109ca2eea5 100644 Binary files a/icons/mob/human_face_m.dmi and b/icons/mob/human_face_m.dmi differ diff --git a/icons/mob/human_races/cyberlimbs/replikant/replikant.dmi b/icons/mob/human_races/cyberlimbs/replikant/replikant.dmi new file mode 100644 index 0000000000..548eadce32 Binary files /dev/null and b/icons/mob/human_races/cyberlimbs/replikant/replikant.dmi differ diff --git a/icons/mob/human_races/cyberlimbs/replikant/replikant2.dmi b/icons/mob/human_races/cyberlimbs/replikant/replikant2.dmi new file mode 100644 index 0000000000..a1fc17ac38 Binary files /dev/null and b/icons/mob/human_races/cyberlimbs/replikant/replikant2.dmi differ diff --git a/icons/mob/human_races/markings_vr.dmi b/icons/mob/human_races/markings_vr.dmi index 37d9c6cf47..df7e6252aa 100644 Binary files a/icons/mob/human_races/markings_vr.dmi and b/icons/mob/human_races/markings_vr.dmi differ diff --git a/icons/mob/items/lefthand_hats.dmi b/icons/mob/items/lefthand_hats.dmi index bf46837f84..abf3b7f620 100644 Binary files a/icons/mob/items/lefthand_hats.dmi and b/icons/mob/items/lefthand_hats.dmi differ diff --git a/icons/mob/items/righthand_hats.dmi b/icons/mob/items/righthand_hats.dmi index 29fa46122b..40cdb7a3e4 100644 Binary files a/icons/mob/items/righthand_hats.dmi and b/icons/mob/items/righthand_hats.dmi differ diff --git a/icons/mob/vore/ears_vr.dmi b/icons/mob/vore/ears_vr.dmi index a7b1f425b0..f49006df1c 100644 Binary files a/icons/mob/vore/ears_vr.dmi and b/icons/mob/vore/ears_vr.dmi differ diff --git a/tgui/.eslintignore b/tgui/.eslintignore index a59187b933..d3c0ac79cd 100644 --- a/tgui/.eslintignore +++ b/tgui/.eslintignore @@ -3,4 +3,14 @@ /**/*.bundle.* /**/*.chunk.* /**/*.hot-update.* -/packages/inferno/** +**.lock +**.log +**.json +**.svg +**.scss +**.md +**.css +**.txt +**.woff2 +**.eot +**.ttf diff --git a/tgui/.eslintrc-harder.yml b/tgui/.eslintrc-harder.yml deleted file mode 100644 index d466125967..0000000000 --- a/tgui/.eslintrc-harder.yml +++ /dev/null @@ -1,43 +0,0 @@ -rules: - ## Enforce a maximum cyclomatic complexity allowed in a program - # complexity: [warn, { max: 25 }] - ## Enforce consistent brace style for blocks - # brace-style: [warn, stroustrup, { allowSingleLine: false }] - ## Enforce the consistent use of either backticks, double, or single quotes - # quotes: [warn, single, { - # avoidEscape: true, - # allowTemplateLiterals: true, - # }] - # react/jsx-closing-bracket-location: [warn, { - # selfClosing: after-props, - # nonEmpty: after-props, - # }] - # react/display-name: warn - - ## Radar - ## ------------------------------------------------------ - # radar/cognitive-complexity: warn - radar/max-switch-cases: warn - radar/no-all-duplicated-branches: warn - radar/no-collapsible-if: warn - radar/no-collection-size-mischeck: warn - radar/no-duplicate-string: warn - radar/no-duplicated-branches: warn - radar/no-element-overwrite: warn - radar/no-extra-arguments: warn - radar/no-identical-conditions: warn - radar/no-identical-expressions: warn - radar/no-identical-functions: warn - radar/no-inverted-boolean-check: warn - radar/no-one-iteration-loop: warn - radar/no-redundant-boolean: warn - radar/no-redundant-jump: warn - radar/no-same-line-conditional: warn - radar/no-small-switch: warn - radar/no-unused-collection: warn - radar/no-use-of-empty-return-value: warn - radar/no-useless-catch: warn - radar/prefer-immediate-return: warn - radar/prefer-object-literal: warn - radar/prefer-single-boolean-return: warn - radar/prefer-while: warn diff --git a/tgui/.eslintrc-sonar.yml b/tgui/.eslintrc-sonar.yml new file mode 100644 index 0000000000..ebc57f72c0 --- /dev/null +++ b/tgui/.eslintrc-sonar.yml @@ -0,0 +1 @@ +extends: 'plugin:sonarjs/recommended' diff --git a/tgui/.eslintrc.yml b/tgui/.eslintrc.yml index 4f7e8f02df..92dfe1320c 100644 --- a/tgui/.eslintrc.yml +++ b/tgui/.eslintrc.yml @@ -11,14 +11,14 @@ env: browser: true node: true plugins: - - radar + - sonarjs - react - unused-imports + - simple-import-sort settings: react: - version: '16.10' + version: '18.2' rules: - ## Possible Errors ## ---------------------------------------- ## Enforce “for” loop update clause moving the counter in the right @@ -309,13 +309,16 @@ rules: ## Enforce or disallow capitalization of the first letter of a comment # capitalized-comments: error ## Require or disallow trailing commas - comma-dangle: [error, { - arrays: always-multiline, - objects: always-multiline, - imports: always-multiline, - exports: always-multiline, - functions: only-multiline, ## Optional on functions - }] + comma-dangle: [ + error, + { + arrays: always-multiline, + objects: always-multiline, + imports: always-multiline, + exports: always-multiline, + functions: only-multiline, ## Optional on functions + }, + ] ## Enforce consistent spacing before and after commas comma-spacing: [error, { before: false, after: true }] ## Enforce consistent comma style @@ -335,7 +338,7 @@ rules: ## Require or disallow named function expressions # func-names: error ## Enforce the consistent use of either function declarations or expressions - func-style: [error, expression] + # func-style: [error, expression] ## Enforce line breaks between arguments of a function call # function-call-argument-newline: error ## Enforce consistent line breaks inside function parentheses @@ -350,15 +353,15 @@ rules: ## Enforce the location of arrow function bodies # implicit-arrow-linebreak: error ## Enforce consistent indentation - # indent: [warn, 2, { SwitchCase: 1 }] + # indent: [error, 2, { SwitchCase: 1 }] ## Enforce the consistent use of either double or single quotes in JSX ## attributes - # jsx-quotes: [warn, prefer-double] + # jsx-quotes: [error, prefer-double] ## Enforce consistent spacing between keys and values in object literal ## properties - # key-spacing: [warn, { beforeColon: false, afterColon: true }] + # key-spacing: [error, { beforeColon: false, afterColon: true }] ## Enforce consistent spacing before and after keywords - # keyword-spacing: [warn, { before: true, after: true }] + # keyword-spacing: [error, { before: true, after: true }] ## Enforce position of line comments # line-comment-position: error ## Enforce consistent linebreak style @@ -370,8 +373,8 @@ rules: ## Enforce a maximum depth that blocks can be nested # max-depth: error ## Enforce a maximum line length - # max-len: [warn, { - # code: 120, + # max-len: [error, { + # code: 80, # ## Ignore imports # ignorePattern: '^(import\s.+\sfrom\s|.*require\()', # ignoreUrls: true, @@ -415,7 +418,7 @@ rules: ## Disallow mixed binary operators # no-mixed-operators: error ## Disallow mixed spaces and tabs for indentation - no-mixed-spaces-and-tabs: warn + # no-mixed-spaces-and-tabs: error ## Disallow use of chained assignment expressions # no-multi-assign: error ## Disallow multiple empty lines @@ -441,7 +444,7 @@ rules: ## Disallow ternary operators when simpler alternatives exist # no-unneeded-ternary: error ## Disallow whitespace before properties - # no-whitespace-before-property: warn + # no-whitespace-before-property: error ## Enforce the location of single-line statements # nonblock-statement-body-position: error ## Enforce consistent line breaks inside braces @@ -458,7 +461,7 @@ rules: ## Require or disallow assignment operator shorthand where possible # operator-assignment: error ## Enforce consistent linebreak style for operators - # operator-linebreak: [warn, before] + # operator-linebreak: [error, before] ## Require or disallow padding within blocks # padded-blocks: error ## Require or disallow padding lines between statements @@ -483,7 +486,7 @@ rules: ## Enforce consistent spacing before blocks space-before-blocks: [error, always] ## Enforce consistent spacing before function definition opening parenthesis - # space-before-function-paren: [warn, { + # space-before-function-paren: [error, { # anonymous: always, # named: never, # asyncArrow: always, @@ -696,7 +699,7 @@ rules: react/jsx-closing-tag-location: error ## Enforce or disallow newlines inside of curly braces in JSX attributes and ## expressions (fixable) - # react/jsx-curly-newline: warn + # react/jsx-curly-newline: error ## Enforce or disallow spaces inside of curly braces in JSX attributes and ## expressions (fixable) react/jsx-curly-spacing: error @@ -709,13 +712,13 @@ rules: ## Enforce event handler naming conventions in JSX react/jsx-handler-names: error ## Validate JSX indentation (fixable) - # react/jsx-indent: [warn, 2, { + # react/jsx-indent: [error, 2, { # checkAttributes: true, # }] ## Validate props indentation in JSX (fixable) - # react/jsx-indent-props: [warn, 2] + # react/jsx-indent-props: [error, 2] ## Validate JSX has key prop when in array or iterator - # react/jsx-key: warn + react/jsx-key: error ## Validate JSX maximum depth react/jsx-max-depth: [error, { max: 10 }] ## Generous ## Limit maximum of props on a single line in JSX (fixable) @@ -762,3 +765,6 @@ rules: ## Prevents the use of unused imports. ## This could be done by enabling no-unused-vars, but we're doing this for now unused-imports/no-unused-imports: error + ## https://github.com/lydell/eslint-plugin-simple-import-sort/ + simple-import-sort/imports: error + simple-import-sort/exports: error diff --git a/tgui/.gitignore b/tgui/.gitignore index 7214051f09..eb4f718b1b 100644 --- a/tgui/.gitignore +++ b/tgui/.gitignore @@ -17,6 +17,7 @@ package-lock.json /public/*.map /public/tgui-bench.bundle.js /public/tgui-bench.bundle.css +/coverage ## Previously ignored locations that are kept to avoid confusing git ## while transitioning to a new project structure. diff --git a/tgui/.prettierrc.yml b/tgui/.prettierrc.yml index 1eebe6098b..0176969226 100644 --- a/tgui/.prettierrc.yml +++ b/tgui/.prettierrc.yml @@ -1,15 +1 @@ -arrowParens: always -breakLongMethodChains: true -endOfLine: lf -importFormatting: oneline -jsxBracketSameLine: true -jsxSingleQuote: false -offsetTernaryExpressions: true -printWidth: 80 -proseWrap: preserve -quoteProps: preserve -semi: true singleQuote: true -tabWidth: 2 -trailingComma: es5 -useTabs: false diff --git a/tgui/.swcrc b/tgui/.swcrc new file mode 100644 index 0000000000..c0402a41f0 --- /dev/null +++ b/tgui/.swcrc @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/swcrc", + "jsc": { + "loose": true, + "parser": { + "syntax": "typescript", + "tsx": true + }, + "transform": { + "react": { + "runtime": "automatic" + } + } + } +} diff --git a/tgui/.yarn/sdks/eslint/package.json b/tgui/.yarn/sdks/eslint/package.json index 744a773210..b29322a1ff 100644 --- a/tgui/.yarn/sdks/eslint/package.json +++ b/tgui/.yarn/sdks/eslint/package.json @@ -2,5 +2,8 @@ "name": "eslint", "version": "7.32.0-sdk", "main": "./lib/api.js", - "type": "commonjs" + "type": "commonjs", + "bin": { + "eslint": "./bin/eslint.js" + } } diff --git a/tgui/.yarn/sdks/prettier/index.cjs b/tgui/.yarn/sdks/prettier/index.cjs new file mode 100644 index 0000000000..d42808285a --- /dev/null +++ b/tgui/.yarn/sdks/prettier/index.cjs @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const { existsSync } = require(`fs`); +const { createRequire, createRequireFromPath } = require(`module`); +const { resolve } = require(`path`); + +const relPnpApiPath = '../../../.pnp.cjs'; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require prettier + require(absPnpApiPath).setup(); + } +} + +// Defer to the real prettier your application uses +module.exports = absRequire(`prettier`); diff --git a/tgui/.yarn/sdks/prettier/index.js b/tgui/.yarn/sdks/prettier/index.js deleted file mode 100644 index f6882d8097..0000000000 --- a/tgui/.yarn/sdks/prettier/index.js +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env node - -const {existsSync} = require(`fs`); -const {createRequire, createRequireFromPath} = require(`module`); -const {resolve} = require(`path`); - -const relPnpApiPath = "../../../.pnp.cjs"; - -const absPnpApiPath = resolve(__dirname, relPnpApiPath); -const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); - -if (existsSync(absPnpApiPath)) { - if (!process.versions.pnp) { - // Setup the environment to be able to require prettier/index.js - require(absPnpApiPath).setup(); - } -} - -// Defer to the real prettier/index.js your application uses -module.exports = absRequire(`prettier/index.js`); diff --git a/tgui/.yarn/sdks/prettier/package.json b/tgui/.yarn/sdks/prettier/package.json index 0cbd71ff32..c61f5117ba 100644 --- a/tgui/.yarn/sdks/prettier/package.json +++ b/tgui/.yarn/sdks/prettier/package.json @@ -1,6 +1,7 @@ { "name": "prettier", - "version": "0.19.0-sdk", - "main": "./index.js", - "type": "commonjs" + "version": "3.1.0-sdk", + "main": "./index.cjs", + "type": "commonjs", + "bin": "./bin/prettier.cjs" } diff --git a/tgui/.yarn/sdks/prettier/prettier.cjs b/tgui/.yarn/sdks/prettier/prettier.cjs new file mode 100644 index 0000000000..5efad688e7 --- /dev/null +++ b/tgui/.yarn/sdks/prettier/prettier.cjs @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require prettier/bin/prettier.cjs + require(absPnpApiPath).setup(); + } +} + +// Defer to the real prettier/bin/prettier.cjs your application uses +module.exports = absRequire(`prettier/bin/prettier.cjs`); diff --git a/tgui/.yarn/sdks/typescript/lib/tsserver.js b/tgui/.yarn/sdks/typescript/lib/tsserver.js index 9f9f4d6f46..e1adb497b6 100644 --- a/tgui/.yarn/sdks/typescript/lib/tsserver.js +++ b/tgui/.yarn/sdks/typescript/lib/tsserver.js @@ -1,29 +1,31 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, createRequireFromPath} = require(`module`); -const {resolve} = require(`path`); +const { existsSync } = require(`fs`); +const { createRequire, createRequireFromPath } = require(`module`); +const { resolve } = require(`path`); -const relPnpApiPath = "../../../../.pnp.cjs"; +const relPnpApiPath = '../../../../.pnp.cjs'; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); -const moduleWrapper = tsserver => { +const moduleWrapper = (tsserver) => { if (!process.versions.pnp) { return tsserver; } - const {isAbsolute} = require(`path`); + const { isAbsolute } = require(`path`); const pnpApi = require(`pnpapi`); - const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); - const isPortal = str => str.startsWith("portal:/"); - const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + const isVirtual = (str) => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = (str) => str.startsWith('portal:/'); + const normalize = (str) => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); - const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { - return `${locator.name}@${locator.reference}`; - })); + const dependencyTreeRoots = new Set( + pnpApi.getDependencyTreeRoots().map((locator) => { + return `${locator.name}@${locator.reference}`; + }) + ); // VSCode sends the zip paths to TS using the "zip://" prefix, that TS // doesn't understand. This layer makes sure to remove the protocol @@ -31,7 +33,11 @@ const moduleWrapper = tsserver => { function toEditorPath(str) { // We add the `zip:` prefix to both `.zip/` paths and virtual paths - if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { + if ( + isAbsolute(str) && + !str.match(/^\^?(zip:|\/zip\/)/) && + (str.match(/\.zip\//) || isVirtual(str)) + ) { // We also take the opportunity to turn virtual paths into physical ones; // this makes it much easier to work with workspaces that list peer // dependencies, since otherwise Ctrl+Click would bring us to the virtual @@ -45,7 +51,11 @@ const moduleWrapper = tsserver => { const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; if (resolved) { const locator = pnpApi.findPackageLocator(resolved); - if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { + if ( + locator && + (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || + isPortal(locator.reference)) + ) { str = resolved; } } @@ -73,42 +83,58 @@ const moduleWrapper = tsserver => { // Before | ^/zip/c:/foo/bar.zip/package.json // After | ^/zip//c:/foo/bar.zip/package.json // - case `vscode <1.61`: { - str = `^zip:${str}`; - } break; + case `vscode <1.61`: + { + str = `^zip:${str}`; + } + break; - case `vscode <1.66`: { - str = `^/zip/${str}`; - } break; + case `vscode <1.66`: + { + str = `^/zip/${str}`; + } + break; - case `vscode <1.68`: { - str = `^/zip${str}`; - } break; + case `vscode <1.68`: + { + str = `^/zip${str}`; + } + break; - case `vscode`: { - str = `^/zip/${str}`; - } break; + case `vscode`: + { + str = `^/zip/${str}`; + } + break; // To make "go to definition" work, // We have to resolve the actual file system path from virtual path // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) - case `coc-nvim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = resolve(`zipfile:${str}`); - } break; + case `coc-nvim`: + { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } + break; // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) // We have to resolve the actual file system path from virtual path, // everything else is up to neovim - case `neovim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = `zipfile://${str}`; - } break; + case `neovim`: + { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } + break; - default: { - str = `zip:${str}`; - } break; + default: + { + str = `zip:${str}`; + } + break; } + } else { + str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); } } @@ -117,26 +143,35 @@ const moduleWrapper = tsserver => { function fromEditorPath(str) { switch (hostInfo) { - case `coc-nvim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for coc-nvim is in format of //zipfile://.yarn/... - // So in order to convert it back, we use .* to match all the thing - // before `zipfile:` - return process.platform === `win32` - ? str.replace(/^.*zipfile:\//, ``) - : str.replace(/^.*zipfile:/, ``); - } break; + case `coc-nvim`: + { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } + break; - case `neovim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for neovim is in format of zipfile:////.yarn/... - return str.replace(/^zipfile:\/\//, ``); - } break; + case `neovim`: + { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } + break; case `vscode`: - default: { - return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) - } break; + default: + { + return str.replace( + /^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, + process.platform === `win32` ? `` : `/` + ); + } + break; } } @@ -148,8 +183,9 @@ const moduleWrapper = tsserver => { // TypeScript already does local loads and if this code is running the user trusts the workspace // https://github.com/microsoft/vscode/issues/45856 const ConfiguredProject = tsserver.server.ConfiguredProject; - const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; - ConfiguredProject.prototype.enablePluginsWithOptions = function() { + const { enablePluginsWithOptions: originalEnablePluginsWithOptions } = + ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function () { this.projectService.allowLocalPluginLoads = true; return originalEnablePluginsWithOptions.apply(this, arguments); }; @@ -159,7 +195,8 @@ const moduleWrapper = tsserver => { // like an absolute path of ours and normalize it. const Session = tsserver.server.Session; - const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + const { onMessage: originalOnMessage, send: originalSend } = + Session.prototype; let hostInfo = `unknown`; Object.assign(Session.prototype, { @@ -175,10 +212,12 @@ const moduleWrapper = tsserver => { ) { hostInfo = parsedMessage.arguments.hostInfo; if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { - const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( - // The RegExp from https://semver.org/ but without the caret at the start - /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - ) ?? []).map(Number) + const [, major, minor] = ( + process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + ) ?? [] + ).map(Number); if (major === 1) { if (minor < 61) { @@ -192,21 +231,31 @@ const moduleWrapper = tsserver => { } } - const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { - return typeof value === 'string' ? fromEditorPath(value) : value; - }); + const processedMessageJSON = JSON.stringify( + parsedMessage, + (key, value) => { + return typeof value === 'string' ? fromEditorPath(value) : value; + } + ); return originalOnMessage.call( this, - isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + isStringMessage + ? processedMessageJSON + : JSON.parse(processedMessageJSON) ); }, send(/** @type {any} */ msg) { - return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { - return typeof value === `string` ? toEditorPath(value) : value; - }))); - } + return originalSend.call( + this, + JSON.parse( + JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }) + ) + ); + }, }); return tsserver; diff --git a/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js b/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js index 878b11946a..1fc12fde93 100644 --- a/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js +++ b/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js @@ -1,29 +1,31 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, createRequireFromPath} = require(`module`); -const {resolve} = require(`path`); +const { existsSync } = require(`fs`); +const { createRequire, createRequireFromPath } = require(`module`); +const { resolve } = require(`path`); -const relPnpApiPath = "../../../../.pnp.cjs"; +const relPnpApiPath = '../../../../.pnp.cjs'; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); -const moduleWrapper = tsserver => { +const moduleWrapper = (tsserver) => { if (!process.versions.pnp) { return tsserver; } - const {isAbsolute} = require(`path`); + const { isAbsolute } = require(`path`); const pnpApi = require(`pnpapi`); - const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); - const isPortal = str => str.startsWith("portal:/"); - const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + const isVirtual = (str) => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = (str) => str.startsWith('portal:/'); + const normalize = (str) => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); - const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { - return `${locator.name}@${locator.reference}`; - })); + const dependencyTreeRoots = new Set( + pnpApi.getDependencyTreeRoots().map((locator) => { + return `${locator.name}@${locator.reference}`; + }) + ); // VSCode sends the zip paths to TS using the "zip://" prefix, that TS // doesn't understand. This layer makes sure to remove the protocol @@ -31,7 +33,11 @@ const moduleWrapper = tsserver => { function toEditorPath(str) { // We add the `zip:` prefix to both `.zip/` paths and virtual paths - if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { + if ( + isAbsolute(str) && + !str.match(/^\^?(zip:|\/zip\/)/) && + (str.match(/\.zip\//) || isVirtual(str)) + ) { // We also take the opportunity to turn virtual paths into physical ones; // this makes it much easier to work with workspaces that list peer // dependencies, since otherwise Ctrl+Click would bring us to the virtual @@ -45,7 +51,11 @@ const moduleWrapper = tsserver => { const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; if (resolved) { const locator = pnpApi.findPackageLocator(resolved); - if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { + if ( + locator && + (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || + isPortal(locator.reference)) + ) { str = resolved; } } @@ -73,42 +83,58 @@ const moduleWrapper = tsserver => { // Before | ^/zip/c:/foo/bar.zip/package.json // After | ^/zip//c:/foo/bar.zip/package.json // - case `vscode <1.61`: { - str = `^zip:${str}`; - } break; + case `vscode <1.61`: + { + str = `^zip:${str}`; + } + break; - case `vscode <1.66`: { - str = `^/zip/${str}`; - } break; + case `vscode <1.66`: + { + str = `^/zip/${str}`; + } + break; - case `vscode <1.68`: { - str = `^/zip${str}`; - } break; + case `vscode <1.68`: + { + str = `^/zip${str}`; + } + break; - case `vscode`: { - str = `^/zip/${str}`; - } break; + case `vscode`: + { + str = `^/zip/${str}`; + } + break; // To make "go to definition" work, // We have to resolve the actual file system path from virtual path // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) - case `coc-nvim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = resolve(`zipfile:${str}`); - } break; + case `coc-nvim`: + { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } + break; // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) // We have to resolve the actual file system path from virtual path, // everything else is up to neovim - case `neovim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = `zipfile://${str}`; - } break; + case `neovim`: + { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } + break; - default: { - str = `zip:${str}`; - } break; + default: + { + str = `zip:${str}`; + } + break; } + } else { + str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); } } @@ -117,26 +143,35 @@ const moduleWrapper = tsserver => { function fromEditorPath(str) { switch (hostInfo) { - case `coc-nvim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for coc-nvim is in format of //zipfile://.yarn/... - // So in order to convert it back, we use .* to match all the thing - // before `zipfile:` - return process.platform === `win32` - ? str.replace(/^.*zipfile:\//, ``) - : str.replace(/^.*zipfile:/, ``); - } break; + case `coc-nvim`: + { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } + break; - case `neovim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for neovim is in format of zipfile:////.yarn/... - return str.replace(/^zipfile:\/\//, ``); - } break; + case `neovim`: + { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } + break; case `vscode`: - default: { - return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) - } break; + default: + { + return str.replace( + /^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, + process.platform === `win32` ? `` : `/` + ); + } + break; } } @@ -148,8 +183,9 @@ const moduleWrapper = tsserver => { // TypeScript already does local loads and if this code is running the user trusts the workspace // https://github.com/microsoft/vscode/issues/45856 const ConfiguredProject = tsserver.server.ConfiguredProject; - const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; - ConfiguredProject.prototype.enablePluginsWithOptions = function() { + const { enablePluginsWithOptions: originalEnablePluginsWithOptions } = + ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function () { this.projectService.allowLocalPluginLoads = true; return originalEnablePluginsWithOptions.apply(this, arguments); }; @@ -159,7 +195,8 @@ const moduleWrapper = tsserver => { // like an absolute path of ours and normalize it. const Session = tsserver.server.Session; - const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + const { onMessage: originalOnMessage, send: originalSend } = + Session.prototype; let hostInfo = `unknown`; Object.assign(Session.prototype, { @@ -175,10 +212,12 @@ const moduleWrapper = tsserver => { ) { hostInfo = parsedMessage.arguments.hostInfo; if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { - const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( - // The RegExp from https://semver.org/ but without the caret at the start - /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - ) ?? []).map(Number) + const [, major, minor] = ( + process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + ) ?? [] + ).map(Number); if (major === 1) { if (minor < 61) { @@ -192,21 +231,31 @@ const moduleWrapper = tsserver => { } } - const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { - return typeof value === 'string' ? fromEditorPath(value) : value; - }); + const processedMessageJSON = JSON.stringify( + parsedMessage, + (key, value) => { + return typeof value === 'string' ? fromEditorPath(value) : value; + } + ); return originalOnMessage.call( this, - isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + isStringMessage + ? processedMessageJSON + : JSON.parse(processedMessageJSON) ); }, send(/** @type {any} */ msg) { - return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { - return typeof value === `string` ? toEditorPath(value) : value; - }))); - } + return originalSend.call( + this, + JSON.parse( + JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }) + ) + ); + }, }); return tsserver; diff --git a/tgui/.yarn/sdks/typescript/lib/typescript.js b/tgui/.yarn/sdks/typescript/lib/typescript.js index cbdbf1500f..384f448c1f 100644 --- a/tgui/.yarn/sdks/typescript/lib/typescript.js +++ b/tgui/.yarn/sdks/typescript/lib/typescript.js @@ -1,20 +1,20 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, createRequireFromPath} = require(`module`); -const {resolve} = require(`path`); +const { existsSync } = require(`fs`); +const { createRequire, createRequireFromPath } = require(`module`); +const { resolve } = require(`path`); -const relPnpApiPath = "../../../../.pnp.cjs"; +const relPnpApiPath = '../../../../.pnp.cjs'; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); if (existsSync(absPnpApiPath)) { if (!process.versions.pnp) { - // Setup the environment to be able to require typescript/lib/typescript.js + // Setup the environment to be able to require typescript require(absPnpApiPath).setup(); } } -// Defer to the real typescript/lib/typescript.js your application uses -module.exports = absRequire(`typescript/lib/typescript.js`); +// Defer to the real typescript your application uses +module.exports = absRequire(`typescript`); diff --git a/tgui/.yarn/sdks/typescript/package.json b/tgui/.yarn/sdks/typescript/package.json index ea85e133e8..9d77f1ac3a 100644 --- a/tgui/.yarn/sdks/typescript/package.json +++ b/tgui/.yarn/sdks/typescript/package.json @@ -2,5 +2,9 @@ "name": "typescript", "version": "4.3.5-sdk", "main": "./lib/typescript.js", - "type": "commonjs" + "type": "commonjs", + "bin": { + "tsc": "./bin/tsc", + "tsserver": "./bin/tsserver" + } } diff --git a/tgui/README.md b/tgui/README.md index c0ff51f2e9..ad79758279 100644 --- a/tgui/README.md +++ b/tgui/README.md @@ -22,12 +22,9 @@ start with our [practical tutorial](docs/tutorial-and-examples.md). ### Guides -This project uses **Inferno** - a very fast UI rendering engine with a similar -API to React. Take your time to read these guides: +This project uses React. Take your time to read the guide: -- [React guide](https://reactjs.org/docs/hello-world.html) -- [Inferno documentation](https://infernojs.org/docs/guides/components) - -highlights differences with React. +- [React guide](https://react.dev/learn) If you were already familiar with an older, Ractive-based tgui, and want to translate concepts between old and new tgui, read this @@ -164,7 +161,7 @@ Press `F12` to open the KitchenSink interface. This interface is a playground to test various tgui components. **Layout Debugger.** -Press `F11` to toggle the *layout debugger*. It will show outlines of +Press `F11` to toggle the _layout debugger_. It will show outlines of all tgui elements, which makes it easy to understand how everything comes together, and can reveal certain layout bugs which are not normally visible. diff --git a/tgui/babel.config.js b/tgui/babel.config.js deleted file mode 100644 index e702c9a711..0000000000 --- a/tgui/babel.config.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -const createBabelConfig = (options) => { - const { presets = [], plugins = [], removeConsole } = options; - // prettier-ignore - return { - presets: [ - [require.resolve('@babel/preset-typescript'), { - allowDeclareFields: true, - }], - [require.resolve('@babel/preset-env'), { - modules: 'commonjs', - useBuiltIns: 'entry', - corejs: '3', - spec: false, - loose: true, - targets: [], - }], - ...presets, - ].filter(Boolean), - plugins: [ - [require.resolve('@babel/plugin-proposal-class-properties'), { - loose: true, - }], - require.resolve('@babel/plugin-transform-jscript'), - require.resolve('babel-plugin-inferno'), - removeConsole && require.resolve('babel-plugin-transform-remove-console'), - require.resolve('common/string.babel-plugin.cjs'), - ...plugins, - ].filter(Boolean), - }; -}; - -module.exports = (api) => { - api.cache(true); - const mode = process.env.NODE_ENV; - return createBabelConfig({ mode }); -}; - -module.exports.createBabelConfig = createBabelConfig; diff --git a/tgui/bin/tgui_.ps1 b/tgui/bin/tgui_.ps1 index b451ac0672..eb9c7a1a97 100644 --- a/tgui/bin/tgui_.ps1 +++ b/tgui/bin/tgui_.ps1 @@ -59,7 +59,7 @@ function task-prettier { } function task-prettify { - yarn prettierx --write packages @Args + yarn prettier --write packages @Args } ## Run a linter through all packages diff --git a/tgui/docs/component-reference.md b/tgui/docs/component-reference.md index 05ec6ef56f..98e03d1be6 100644 --- a/tgui/docs/component-reference.md +++ b/tgui/docs/component-reference.md @@ -65,19 +65,13 @@ it is used a lot in this framework. **Event handlers.** Event handlers are callbacks that you can attack to various element to -listen for browser events. Inferno supports camelcase (`onClick`) and -lowercase (`onclick`) event names. +listen for browser events. React supports camelcase (`onClick`) event names. - Camel case names are what's called *synthetic* events, and are the **preferred way** of handling events in React, for efficiency and performance reasons. Please read -[Inferno Event Handling](https://infernojs.org/docs/guides/event-handling) +[React Event Handling](https://react.dev/learn/responding-to-events) to understand what this is about. -- Lower case names are native browser events and should be used sparingly, -for example when you need an explicit IE8 support. **DO NOT** use -lowercase event handlers unless you really know what you are doing. -- [Button](#button) component does not support the lowercase `onclick` event. -Use the camel case `onClick` instead. ## `tgui/components` @@ -746,9 +740,10 @@ Popper lets you position elements so that they don't go out of the bounds of the **Props:** -- `popperContent: InfernoNode` - The content that will be put inside the popper. -- `options?: { ... }` - An object of options to pass to `createPopper`. See [https://popper.js.org/docs/v2/constructors/#options], but the one you want most is `placement`. Valid placements are "bottom", "top", "left", and "right". You can affix "-start" and "-end" to achieve something like top left or top right respectively. You can also use "auto" (with an optional "-start" or "-end"), where a best fit will be chosen. -- `additionalStyles: { ... }` - A map of CSS styles to add to the element that will contain the popper. +- `content: ReactNode` - The content that will be put inside the popper. +- `isOpen: boolean` - Whether or not the popper is open. +- `onClickOutside?: (e) => void` - A function that will be called when the user clicks outside of the popper. +- `placement?: string` - The placement of the popper. See [https://popper.js.org/docs/v2/constructors/#placement] ### `ProgressBar` diff --git a/tgui/docs/state-usage.md b/tgui/docs/state-usage.md new file mode 100644 index 0000000000..da3162554b --- /dev/null +++ b/tgui/docs/state-usage.md @@ -0,0 +1,38 @@ +# Managing component state + +React has excellent documentation on useState and useEffect. These hooks should be the ways to manage state in TGUI (v5). +[React Hooks](https://react.dev/learn/state-a-components-memory) + +You might find usages of useLocalState. This should be considered deprecated and will be removed in the future. In older versions of TGUI, InfernoJS did not have hooks, so these were used to manage state. useSharedState is still used in some places where uis are considered "IC" and user input is shared with all persons at the console/machine/thing. + +## A Note on State + +Many beginners tend to overuse state (or hooks all together). State is effective when you want to implement user interactivity, or are handling asynchronous data, but if you are simply using state to store a value that is not changing, you should consider using a variable instead. + +In previous versions of React, each setState would trigger a re-render, which would cause poorly written components to cascade re-render on each page load. Messy! Though this is no longer the case with batch rendering, it's still worthwhile to point out that you might be overusing it. + +## Derived state + +One great way to cut back on state usage is by using props or other state as the basis for a variable. You'll see many examples of this in the TGUI codebase. What does this mean? Here's an example: + +```tsx +// Bad +const [count, setCount] = useState(0); +const [isEven, setIsEven] = useState(false); + +useEffect(() => { + setIsEven(count % 2 === 0); +}, [count]); + +// Good! +const [count, setCount] = useState(0); +const isEven = count % 2 === 0; // Derived state +``` +1 change: 1 addition & 0 deletions 1 +tgui/packages/tgui/backend.ts +Viewed +@@ -316,6 +316,7 @@ type StateWithSetter = [T, (nextState: T) => void]; + * @param context React context. + * @param key Key which uniquely identifies this state in Redux store. + * @param initialState Initializes your global variable with this value. + * diff --git a/tgui/jest.config.js b/tgui/jest.config.js index 8b78818004..d8b4ac3e41 100644 --- a/tgui/jest.config.js +++ b/tgui/jest.config.js @@ -8,7 +8,7 @@ module.exports = { testEnvironment: 'jsdom', testRunner: require.resolve('jest-circus/runner'), transform: { - '^.+\\.(js|cjs|ts|tsx)$': require.resolve('babel-jest'), + '^.+\\.(js|cjs|ts|tsx)$': require.resolve('@swc/jest'), }, moduleFileExtensions: ['js', 'cjs', 'ts', 'tsx', 'json'], resetMocks: true, diff --git a/tgui/package.json b/tgui/package.json index a3bdcb4d90..1370aacc48 100644 --- a/tgui/package.json +++ b/tgui/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "tgui-workspace", - "version": "4.3.1", + "version": "5.0.0", "packageManager": "yarn@3.3.1", "workspaces": [ "packages/*" @@ -9,54 +9,48 @@ "scripts": { "tgui:analyze": "webpack --analyze", "tgui:bench": "webpack --env TGUI_BENCH=1 && node packages/tgui-bench/index.js", - "tgui:build": "webpack", + "tgui:build": "BROWSERSLIST_IGNORE_OLD_DATA=true webpack", "tgui:dev": "node --experimental-modules packages/tgui-dev-server/index.js", "tgui:lint": "eslint packages --ext .js,.cjs,.ts,.tsx", - "tgui:prettier": "prettierx --check .", - "tgui:sonar": "eslint packages --ext .js,.cjs,.ts,.tsx -c .eslintrc-harder.yml", + "tgui:prettier": "prettier --check .", + "tgui:sonar": "eslint packages -c .eslintrc-sonar.yml", "tgui:test": "jest --watch", "tgui:test-simple": "CI=true jest --color", "tgui:test-ci": "CI=true jest --color --collect-coverage", "tgui:tsc": "tsc" }, "dependencies": { - "@babel/core": "^7.18.0", - "@babel/eslint-parser": "^7.17.0", - "@babel/plugin-proposal-class-properties": "^7.17.12", - "@babel/plugin-transform-jscript": "^7.17.12", - "@babel/preset-env": "^7.18.0", - "@babel/preset-typescript": "^7.17.12", - "@types/jest": "^27.5.1", - "@types/jsdom": "^16.2.14", + "@swc/core": "^1.3.100", + "@swc/jest": "^0.2.29", + "@types/jest": "^29.5.10", + "@types/jsdom": "^21.1.6", "@types/node": "^14.x", - "@types/webpack": "^5.28.0", - "@types/webpack-env": "^1.17.0", - "@typescript-eslint/parser": "^5.25.0", - "babel-jest": "^28.1.0", - "babel-loader": "^8.2.5", - "babel-plugin-inferno": "^6.4.0", - "babel-plugin-transform-remove-console": "^6.9.4", - "common": "workspace:*", - "css-loader": "^6.7.1", + "@types/webpack": "^5.28.5", + "@types/webpack-env": "^1.18.4", + "@typescript-eslint/parser": "^6.14.0", + "css-loader": "^6.8.1", "esbuild-loader": "^4.0.2", - "eslint": "^8.16.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-radar": "^0.2.1", - "eslint-plugin-react": "^7.30.0", - "eslint-plugin-unused-imports": "^2.0.0", - "inferno": "^7.4.11", - "jest": "^28.1.0", - "jest-circus": "^28.1.0", - "jest-environment-jsdom": "^28.1.0", - "jsdom": "^19.0.0", - "mini-css-extract-plugin": "^2.6.0", - "prettier": "npm:prettierx@0.19.0", - "sass": "^1.52.1", - "sass-loader": "^13.0.0", - "style-loader": "^3.3.1", - "typescript": "^4.6.4", - "webpack": "^5.76.0", - "webpack-bundle-analyzer": "^4.5.0", - "webpack-cli": "^4.9.2" + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-simple-import-sort": "^10.0.0", + "eslint-plugin-sonarjs": "^0.23.0", + "eslint-plugin-unused-imports": "^3.0.0", + "file-loader": "^6.2.0", + "jest": "^29.7.0", + "jest-circus": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jsdom": "^22.1.0", + "mini-css-extract-plugin": "^2.7.6", + "prettier": "^3.1.0", + "sass": "^1.69.5", + "sass-loader": "^13.3.2", + "style-loader": "^3.3.3", + "swc-loader": "^0.2.3", + "typescript": "^4.9.4", + "url-loader": "^4.1.1", + "webpack": "^5.89.0", + "webpack-bundle-analyzer": "^4.10.1", + "webpack-cli": "^5.1.4" } } diff --git a/tgui/packages/common/collections.ts b/tgui/packages/common/collections.ts index a005da7aa1..5bfcee8588 100644 --- a/tgui/packages/common/collections.ts +++ b/tgui/packages/common/collections.ts @@ -32,12 +32,12 @@ export const filter = }; type MapFunction = { - (iterateeFn: (value: T, index: number, collection: T[]) => U): ( - collection: T[] - ) => U[]; + ( + iterateeFn: (value: T, index: number, collection: T[]) => U, + ): (collection: T[]) => U[]; ( - iterateeFn: (value: T, index: K, collection: Record) => U + iterateeFn: (value: T, index: K, collection: Record) => U, ): (collection: Record) => U[]; }; @@ -75,7 +75,7 @@ export const map: MapFunction = */ export const filterMap = ( collection: T[], - iterateeFn: (value: T) => U | undefined + iterateeFn: (value: T) => U | undefined, ): U[] => { const finalCollection: U[] = []; @@ -261,7 +261,7 @@ export const zipWith = const binarySearch = ( getKey: (value: T) => U, collection: readonly T[], - inserting: T + inserting: T, ): number => { if (collection.length === 0) { return 0; diff --git a/tgui/packages/common/color.js b/tgui/packages/common/color.js index b59d82247a..59935931d8 100644 --- a/tgui/packages/common/color.js +++ b/tgui/packages/common/color.js @@ -30,7 +30,7 @@ export class Color { this.r - this.r * percent, this.g - this.g * percent, this.b - this.b * percent, - this.a + this.a, ); } @@ -48,7 +48,7 @@ Color.fromHex = (hex) => new Color( parseInt(hex.substr(1, 2), 16), parseInt(hex.substr(3, 2), 16), - parseInt(hex.substr(5, 2), 16) + parseInt(hex.substr(5, 2), 16), ); /** @@ -59,7 +59,7 @@ Color.lerp = (c1, c2, n) => (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 + (c2.a - c1.a) * n + c1.a, ); /** diff --git a/tgui/packages/common/keys.ts b/tgui/packages/common/keys.ts index 61b79992b4..34ac9e1614 100644 --- a/tgui/packages/common/keys.ts +++ b/tgui/packages/common/keys.ts @@ -22,18 +22,18 @@ export enum KEY { Backspace = 'Backspace', Control = 'Control', Delete = 'Delete', - Down = 'Down', + Down = 'ArrowDown', End = 'End', Enter = 'Enter', - Escape = 'Esc', + Escape = 'Escape', Home = 'Home', Insert = 'Insert', - Left = 'Left', + Left = 'ArrowLeft', PageDown = 'PageDown', PageUp = 'PageUp', - Right = 'Right', + Right = 'ArrowRight', Shift = 'Shift', Space = ' ', Tab = 'Tab', - Up = 'Up', + Up = 'ArrowUp', } diff --git a/tgui/packages/common/react.ts b/tgui/packages/common/react.ts index 8e42d0971a..5260ff6ae1 100644 --- a/tgui/packages/common/react.ts +++ b/tgui/packages/common/react.ts @@ -52,13 +52,10 @@ export const shallowDiffers = (a: object, b: object) => { }; /** - * Default inferno hooks for pure components. + * A common case in tgui, when you pass a value conditionally, these are + * the types that can fall through the condition. */ -export const pureComponentHooks = { - onComponentShouldUpdate: (lastProps, nextProps) => { - return shallowDiffers(lastProps, nextProps); - }, -}; +export type BooleanLike = number | boolean | null | undefined; /** * A helper to determine whether the object is renderable by React. @@ -69,9 +66,3 @@ export const canRender = (value: unknown) => { && value !== null && typeof value !== 'boolean'; }; - -/** - * A common case in tgui, when you pass a value conditionally, these are - * the types that can fall through the condition. - */ -export type BooleanLike = number | boolean | null | undefined; diff --git a/tgui/packages/common/redux.test.ts b/tgui/packages/common/redux.test.ts index af4e5d4e73..d4af99907c 100644 --- a/tgui/packages/common/redux.test.ts +++ b/tgui/packages/common/redux.test.ts @@ -1,4 +1,11 @@ -import { Action, Reducer, applyMiddleware, combineReducers, createAction, createStore } from './redux'; +import { + Action, + applyMiddleware, + combineReducers, + createAction, + createStore, + Reducer, +} from './redux'; // Dummy Reducer const counterReducer: Reducer> = (state = 0, action) => { @@ -31,7 +38,7 @@ describe('Redux implementation tests', () => { test('createStore with applyMiddleware works', () => { const store = createStore( counterReducer, - applyMiddleware(loggingMiddleware) + applyMiddleware(loggingMiddleware), ); expect(store.getState()).toBe(0); }); diff --git a/tgui/packages/common/redux.ts b/tgui/packages/common/redux.ts index 1635853c6b..997cc1d2d6 100644 --- a/tgui/packages/common/redux.ts +++ b/tgui/packages/common/redux.ts @@ -6,7 +6,7 @@ export type Reducer = ( state: State | undefined, - action: ActionType + action: ActionType, ) => State; export type Store = { @@ -21,7 +21,7 @@ type MiddlewareAPI = { }; export type Middleware = ( - storeApi: MiddlewareAPI + storeApi: MiddlewareAPI, ) => (next: Dispatch) => Dispatch; export type Action = { @@ -33,7 +33,7 @@ export type AnyAction = Action & { }; export type Dispatch = ( - action: ActionType + action: ActionType, ) => void; type StoreEnhancer = (createStoreFunction: Function) => Function; @@ -48,7 +48,7 @@ type PreparedAction = { */ export const createStore = ( reducer: Reducer, - enhancer?: StoreEnhancer + enhancer?: StoreEnhancer, ): Store => { // Apply a store enhancer (applyMiddleware is one of them). if (enhancer) { @@ -90,14 +90,14 @@ export const applyMiddleware = ( ...middlewares: Middleware[] ): StoreEnhancer => { return ( - createStoreFunction: (reducer: Reducer, enhancer?: StoreEnhancer) => Store + createStoreFunction: (reducer: Reducer, enhancer?: StoreEnhancer) => Store, ) => { return (reducer, ...args): Store => { const store = createStoreFunction(reducer, ...args); let dispatch: Dispatch = () => { throw new Error( - 'Dispatching while constructing your middleware is not allowed.' + 'Dispatching while constructing your middleware is not allowed.', ); }; @@ -109,7 +109,7 @@ export const applyMiddleware = ( const chain = middlewares.map((middleware) => middleware(storeApi)); dispatch = chain.reduceRight( (next, middleware) => middleware(next), - store.dispatch + store.dispatch, ); return { @@ -129,7 +129,7 @@ export const applyMiddleware = ( * is also more flexible than the redux counterpart. */ export const combineReducers = ( - reducersObj: Record + reducersObj: Record, ): Reducer => { const keys = Object.keys(reducersObj); @@ -170,7 +170,7 @@ export const combineReducers = ( */ export const createAction = ( type: TAction, - prepare?: (...args: any[]) => PreparedAction + prepare?: (...args: any[]) => PreparedAction, ) => { const actionCreator = (...args: any[]) => { let action: Action & PreparedAction = { type }; @@ -194,23 +194,3 @@ export const createAction = ( return actionCreator; }; - -// Implementation specific -// -------------------------------------------------------- - -export const useDispatch = (context: { - store: Store; -}): Dispatch => { - return context?.store?.dispatch; -}; - -export const useSelector = ( - context: { store: Store }, - selector: (state: State) => Selected -): Selected => { - if (!context) { - return {} as Selected; - } - - return selector(context?.store?.getState()); -}; diff --git a/tgui/packages/common/timer.ts b/tgui/packages/common/timer.ts index 49d3648420..1fc3e11fd3 100644 --- a/tgui/packages/common/timer.ts +++ b/tgui/packages/common/timer.ts @@ -13,7 +13,7 @@ export const debounce = any>( fn: F, time: number, - immediate = false + immediate = false, ): ((...args: Parameters) => void) => { let timeout: ReturnType | null; return (...args: Parameters) => { @@ -38,7 +38,7 @@ export const debounce = any>( */ export const throttle = any>( fn: F, - time: number + time: number, ): ((...args: Parameters) => void) => { let previouslyRun: number | null, queuedToRun: ReturnType | null; @@ -53,7 +53,7 @@ export const throttle = any>( } else { queuedToRun = setTimeout( () => invokeFn(...args), - time - (now - (previouslyRun ?? 0)) + time - (now - (previouslyRun ?? 0)), ); } }; diff --git a/tgui/packages/tgfont/dist/tgfont.css b/tgui/packages/tgfont/dist/tgfont.css index 7e75af2f05..568f178c2e 100644 --- a/tgui/packages/tgfont/dist/tgfont.css +++ b/tgui/packages/tgfont/dist/tgfont.css @@ -1,6 +1,7 @@ @font-face { font-family: 'tgfont'; - src: url('./tgfont.woff2?958b912b123580c55529f68c4e2261bd') format('woff2'), + src: + url('./tgfont.woff2?958b912b123580c55529f68c4e2261bd') format('woff2'), url('./tgfont.eot?958b912b123580c55529f68c4e2261bd#iefix') format('embedded-opentype'); } diff --git a/tgui/packages/tgui-bench/entrypoint.tsx b/tgui/packages/tgui-bench/entrypoint.tsx index a6eb7a91d6..b160d320b5 100644 --- a/tgui/packages/tgui-bench/entrypoint.tsx +++ b/tgui/packages/tgui-bench/entrypoint.tsx @@ -4,8 +4,10 @@ * @license MIT */ -import { setupGlobalEvents } from 'tgui/events'; import 'tgui/styles/main.scss'; + +import { setupGlobalEvents } from 'tgui/events'; + import Benchmark from './lib/benchmark'; const sendMessage = (obj: any) => { diff --git a/tgui/packages/tgui-bench/lib/benchmark.d.ts b/tgui/packages/tgui-bench/lib/benchmark.d.ts index 7f3310005f..3eac568d41 100644 --- a/tgui/packages/tgui-bench/lib/benchmark.d.ts +++ b/tgui/packages/tgui-bench/lib/benchmark.d.ts @@ -27,7 +27,7 @@ declare class Benchmark { static reduce( arr: T[], callback: (accumulator: K, value: T) => K, - thisArg?: any + thisArg?: any, ): K; static options: Benchmark.Options; diff --git a/tgui/packages/tgui-bench/lib/benchmark.js b/tgui/packages/tgui-bench/lib/benchmark.js index 33a645f294..5cb8b15f59 100644 --- a/tgui/packages/tgui-bench/lib/benchmark.js +++ b/tgui/packages/tgui-bench/lib/benchmark.js @@ -15,8 +15,8 @@ module.exports = function () { /** Used to determine if values are of the language type Object. */ var objectTypes = { - 'function': true, - 'object': true, + function: true, + object: true, }; /** Used as a reference to the global object. */ @@ -58,11 +58,11 @@ module.exports = function () { /** Used to avoid hz of Infinity. */ var divisors = { - '1': 4096, - '2': 512, - '3': 64, - '4': 8, - '5': 0, + 1: 4096, + 2: 512, + 3: 64, + 4: 8, + 5: 0, }; /** @@ -70,37 +70,37 @@ module.exports = function () { * For more info see http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm. */ var tTable = { - '1': 12.706, - '2': 4.303, - '3': 3.182, - '4': 2.776, - '5': 2.571, - '6': 2.447, - '7': 2.365, - '8': 2.306, - '9': 2.262, - '10': 2.228, - '11': 2.201, - '12': 2.179, - '13': 2.16, - '14': 2.145, - '15': 2.131, - '16': 2.12, - '17': 2.11, - '18': 2.101, - '19': 2.093, - '20': 2.086, - '21': 2.08, - '22': 2.074, - '23': 2.069, - '24': 2.064, - '25': 2.06, - '26': 2.056, - '27': 2.052, - '28': 2.048, - '29': 2.045, - '30': 2.042, - 'infinity': 1.96, + 1: 12.706, + 2: 4.303, + 3: 3.182, + 4: 2.776, + 5: 2.571, + 6: 2.447, + 7: 2.365, + 8: 2.306, + 9: 2.262, + 10: 2.228, + 11: 2.201, + 12: 2.179, + 13: 2.16, + 14: 2.145, + 15: 2.131, + 16: 2.12, + 17: 2.11, + 18: 2.101, + 19: 2.093, + 20: 2.086, + 21: 2.08, + 22: 2.074, + 23: 2.069, + 24: 2.064, + 25: 2.06, + 26: 2.056, + 27: 2.052, + 28: 2.048, + 29: 2.045, + 30: 2.042, + infinity: 1.96, }; /** @@ -108,61 +108,61 @@ module.exports = function () { * For more info see http://www.saburchill.com/IBbiology/stats/003.html. */ var uTable = { - '5': [0, 1, 2], - '6': [1, 2, 3, 5], - '7': [1, 3, 5, 6, 8], - '8': [2, 4, 6, 8, 10, 13], - '9': [2, 4, 7, 10, 12, 15, 17], - '10': [3, 5, 8, 11, 14, 17, 20, 23], - '11': [3, 6, 9, 13, 16, 19, 23, 26, 30], - '12': [4, 7, 11, 14, 18, 22, 26, 29, 33, 37], - '13': [4, 8, 12, 16, 20, 24, 28, 33, 37, 41, 45], - '14': [5, 9, 13, 17, 22, 26, 31, 36, 40, 45, 50, 55], - '15': [5, 10, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64], - '16': [6, 11, 15, 21, 26, 31, 37, 42, 47, 53, 59, 64, 70, 75], - '17': [6, 11, 17, 22, 28, 34, 39, 45, 51, 57, 63, 67, 75, 81, 87], - '18': [7, 12, 18, 24, 30, 36, 42, 48, 55, 61, 67, 74, 80, 86, 93, 99], - '19': [7, 13, 19, 25, 32, 38, 45, 52, 58, 65, 72, 78, 85, 92, 99, 106, 113], - '20': [ + 5: [0, 1, 2], + 6: [1, 2, 3, 5], + 7: [1, 3, 5, 6, 8], + 8: [2, 4, 6, 8, 10, 13], + 9: [2, 4, 7, 10, 12, 15, 17], + 10: [3, 5, 8, 11, 14, 17, 20, 23], + 11: [3, 6, 9, 13, 16, 19, 23, 26, 30], + 12: [4, 7, 11, 14, 18, 22, 26, 29, 33, 37], + 13: [4, 8, 12, 16, 20, 24, 28, 33, 37, 41, 45], + 14: [5, 9, 13, 17, 22, 26, 31, 36, 40, 45, 50, 55], + 15: [5, 10, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64], + 16: [6, 11, 15, 21, 26, 31, 37, 42, 47, 53, 59, 64, 70, 75], + 17: [6, 11, 17, 22, 28, 34, 39, 45, 51, 57, 63, 67, 75, 81, 87], + 18: [7, 12, 18, 24, 30, 36, 42, 48, 55, 61, 67, 74, 80, 86, 93, 99], + 19: [7, 13, 19, 25, 32, 38, 45, 52, 58, 65, 72, 78, 85, 92, 99, 106, 113], + 20: [ 8, 14, 20, 27, 34, 41, 48, 55, 62, 69, 76, 83, 90, 98, 105, 112, 119, 127, ], - '21': [ + 21: [ 8, 15, 22, 29, 36, 43, 50, 58, 65, 73, 80, 88, 96, 103, 111, 119, 126, 134, 142, ], - '22': [ + 22: [ 9, 16, 23, 30, 38, 45, 53, 61, 69, 77, 85, 93, 101, 109, 117, 125, 133, 141, 150, 158, ], - '23': [ + 23: [ 9, 17, 24, 32, 40, 48, 56, 64, 73, 81, 89, 98, 106, 115, 123, 132, 140, 149, 157, 166, 175, ], - '24': [ + 24: [ 10, 17, 25, 33, 42, 50, 59, 67, 76, 85, 94, 102, 111, 120, 129, 138, 147, 156, 165, 174, 183, 192, ], - '25': [ + 25: [ 10, 18, 27, 35, 44, 53, 62, 71, 80, 89, 98, 107, 117, 126, 135, 145, 154, 163, 173, 182, 192, 201, 211, ], - '26': [ + 26: [ 11, 19, 28, 37, 46, 55, 64, 74, 83, 93, 102, 112, 122, 132, 141, 151, 161, 171, 181, 191, 200, 210, 220, 230, ], - '27': [ + 27: [ 11, 20, 29, 38, 48, 57, 67, 77, 87, 97, 107, 118, 125, 138, 147, 158, 168, 178, 188, 199, 209, 219, 230, 240, 250, ], - '28': [ + 28: [ 12, 21, 30, 40, 50, 60, 70, 80, 90, 101, 111, 122, 132, 143, 154, 164, 175, 186, 196, 207, 218, 228, 239, 250, 261, 272, ], - '29': [ + 29: [ 13, 22, 32, 42, 52, 62, 73, 83, 94, 105, 116, 127, 138, 149, 160, 171, 182, 193, 204, 215, 226, 238, 249, 260, 271, 282, 294, ], - '30': [ + 30: [ 13, 23, 33, 43, 54, 65, 76, 87, 98, 109, 120, 131, 143, 154, 166, 177, 189, 200, 212, 223, 235, 247, 258, 270, 282, 293, 305, 317, ], @@ -285,12 +285,12 @@ module.exports = function () { ( 'return (' + function (x) { - return { 'x': '' + (1 + x) + '', 'y': 0 }; + return { x: '' + (1 + x) + '', y: 0 }; } + ')' ) // Avoid issues with code added by Istanbul. - .replace(/__cov__[^;]+;/g, '') + .replace(/__cov__[^;]+;/g, ''), )()(0).x === '1'; } catch (e) { support.decompilation = false; @@ -311,7 +311,7 @@ module.exports = function () { * @memberOf timer * @type {Function|Object} */ - 'ns': Date, + ns: Date, /** * Starts the deferred timer. @@ -320,7 +320,7 @@ module.exports = function () { * @memberOf timer * @param {Object} deferred The deferred instance. */ - 'start': null, // Lazy defined in `clock()`. + start: null, // Lazy defined in `clock()`. /** * Stops the deferred timer. @@ -329,7 +329,7 @@ module.exports = function () { * @memberOf timer * @param {Object} deferred The deferred instance. */ - 'stop': null, // Lazy defined in `clock()`. + stop: null, // Lazy defined in `clock()`. }; /*------------------------------------------------------------------------*/ @@ -478,10 +478,10 @@ module.exports = function () { } return event instanceof Event ? _.assign( - event, - { 'timeStamp': _.now() }, - typeof type == 'string' ? { 'type': type } : type - ) + event, + { timeStamp: _.now() }, + typeof type == 'string' ? { type: type } : type, + ) : new Event(type); } @@ -584,7 +584,7 @@ module.exports = function () { args + '){' + body + - '}' + '}', ); result = anchor[prop]; delete anchor[prop]; @@ -672,7 +672,7 @@ module.exports = function () { // Detect strings containing only the "use strict" directive. return /^(?:\/\*+[\w\W]*?\*\/|\/\/.*?[\n\r\u2028\u2029]|\s)*(["'])use strict\1;?$/.test( - result + result, ) ? '' : result; @@ -772,7 +772,7 @@ module.exports = function () { options = object.options = _.assign( {}, cloneDeep(object.constructor.options), - cloneDeep(options) + cloneDeep(options), ); _.forOwn(options, function (value, key) { @@ -927,11 +927,11 @@ module.exports = function () { bench, queued, index = -1, - eventProps = { 'currentTarget': benches }, + eventProps = { currentTarget: benches }, options = { - 'onStart': _.noop, - 'onCycle': _.noop, - 'onComplete': _.noop, + onStart: _.noop, + onCycle: _.noop, + onComplete: _.noop, }, result = _.toArray(benches); @@ -1167,7 +1167,7 @@ module.exports = function () { function add(name, fn, options) { var suite = this, bench = new Benchmark(name, fn, options), - event = Event({ 'type': 'add', 'target': bench }); + event = Event({ type: 'add', target: bench }); if ((suite.emit(event), !event.cancelled)) { suite.push(bench); @@ -1269,21 +1269,21 @@ module.exports = function () { options || (options = {}); invoke(suite, { - 'name': 'run', - 'args': options, - 'queued': options.queued, - 'onStart': function (event) { + name: 'run', + args: options, + queued: options.queued, + onStart: function (event) { suite.emit(event); }, - 'onCycle': function (event) { + onCycle: function (event) { var bench = event.target; if (bench.error) { - suite.emit({ 'type': 'error', 'target': bench }); + suite.emit({ type: 'error', target: bench }); } suite.emit(event); event.aborted = suite.aborted; }, - 'onComplete': function (event) { + onComplete: function (event) { suite.running = false; suite.emit(event); }, @@ -1415,7 +1415,7 @@ module.exports = function () { _.each(type.split(' '), function (type) { (_.has(events, type) ? events[type] : (events[type] = [])).push( - listener + listener, ); }); return object; @@ -1476,7 +1476,7 @@ module.exports = function () { result.options = _.assign( {}, cloneDeep(bench.options), - cloneDeep(options) + cloneDeep(options), ); // Copy own custom properties. @@ -1521,7 +1521,7 @@ module.exports = function () { function (total, xB) { return total + (xB > xA ? 0 : xB < xA ? 1 : 0.5); }, - 0 + 0, ); } @@ -1531,7 +1531,7 @@ module.exports = function () { function (total, xA) { return total + getScore(xA, sampleB); }, - 0 + 0, ); } @@ -1577,11 +1577,11 @@ module.exports = function () { // A non-recursive solution to check if properties have changed. // For more information see http://www.jslab.dk/articles/non.recursive.preorder.traversal.part4. var data = { - 'destination': bench, - 'source': _.assign( + destination: bench, + source: _.assign( {}, cloneDeep(bench.constructor.prototype), - cloneDeep(bench.options) + cloneDeep(bench.options), ), }; @@ -1615,12 +1615,12 @@ module.exports = function () { // Register a changed object. if (changed) { changes.push({ - 'destination': destination, - 'key': key, - 'value': currValue, + destination: destination, + key: key, + value: currValue, }); } - queue.push({ 'destination': currValue, 'source': value }); + queue.push({ destination: currValue, source: value }); } // Register a changed primitive. else if ( @@ -1628,9 +1628,9 @@ module.exports = function () { !(value == null || _.isFunction(value)) ) { changes.push({ - 'destination': destination, - 'key': key, - 'value': value, + destination: destination, + key: key, + value: value, }); } }); @@ -1674,7 +1674,7 @@ module.exports = function () { } else { // Error#name and Error#message properties are non-enumerable. errorStr = join( - _.assign({ 'name': error.name, 'message': error.message }, error) + _.assign({ name: error.name, message: error.message }, error), ); } result += ': ' + errorStr; @@ -1706,9 +1706,7 @@ module.exports = function () { function clock() { var options = Benchmark.options, templateData = {}, - timers = [ - { 'ns': timer.ns, 'res': max(0.0015, getRes('ms')), 'unit': 'ms' }, - ]; + timers = [{ ns: timer.ns, res: max(0.0015, getRes('ms')), unit: 'ms' }]; // Lazy define for hi-res timers. clock = function (clone) { @@ -1740,20 +1738,20 @@ module.exports = function () { // to avoid potential engine optimizations enabled over the life of the test. var funcBody = deferred ? 'var d#=this,${fnArg}=d#,m#=d#.benchmark._original,f#=m#.fn,su#=m#.setup,td#=m#.teardown;' + - // When `deferred.cycles` is `0` then... - 'if(!d#.cycles){' + - // set `deferred.fn`, - 'd#.fn=function(){var ${fnArg}=d#;if(typeof f#=="function"){try{${fn}\n}catch(e#){f#(d#)}}else{${fn}\n}};' + - // set `deferred.teardown`, - 'd#.teardown=function(){d#.cycles=0;if(typeof td#=="function"){try{${teardown}\n}catch(e#){td#()}}else{${teardown}\n}};' + - // execute the benchmark's `setup`, - 'if(typeof su#=="function"){try{${setup}\n}catch(e#){su#()}}else{${setup}\n};' + - // start timer, - 't#.start(d#);' + - // and then execute `deferred.fn` and return a dummy object. - '}d#.fn();return{uid:"${uid}"}' + // When `deferred.cycles` is `0` then... + 'if(!d#.cycles){' + + // set `deferred.fn`, + 'd#.fn=function(){var ${fnArg}=d#;if(typeof f#=="function"){try{${fn}\n}catch(e#){f#(d#)}}else{${fn}\n}};' + + // set `deferred.teardown`, + 'd#.teardown=function(){d#.cycles=0;if(typeof td#=="function"){try{${teardown}\n}catch(e#){td#()}}else{${teardown}\n}};' + + // execute the benchmark's `setup`, + 'if(typeof su#=="function"){try{${setup}\n}catch(e#){su#()}}else{${setup}\n};' + + // start timer, + 't#.start(d#);' + + // and then execute `deferred.fn` and return a dummy object. + '}d#.fn();return{uid:"${uid}"}' : 'var r#,s#,m#=this,f#=m#.fn,i#=m#.count,n#=t#.ns;${setup}\n${begin};' + - 'while(i#--){${fn}\n}${end};${teardown}\nreturn{elapsed:r#,uid:"${uid}"}'; + 'while(i#--){${fn}\n}${end};${teardown}\nreturn{elapsed:r#,uid:"${uid}"}'; var compiled = (bench.compiled = @@ -1768,7 +1766,7 @@ module.exports = function () { throw new Error( 'The test "' + name + - '" is empty. This may be the result of dead code removal.' + '" is empty. This may be the result of dead code removal.', ); } else if (!deferred) { // Pretest to determine if compiled code exits early, usually by a @@ -1833,14 +1831,14 @@ module.exports = function () { templateData.uid = uid + uidCounter++; _.assign(templateData, { - 'setup': decompilable + setup: decompilable ? getSource(bench.setup) : interpolate('m#.setup()'), - 'fn': decompilable + fn: decompilable ? getSource(fn) : interpolate('m#.fn(' + fnArg + ')'), - 'fnArg': fnArg, - 'teardown': decompilable + fnArg: fnArg, + teardown: decompilable ? getSource(bench.teardown) : interpolate('m#.teardown()'), }); @@ -1848,48 +1846,48 @@ module.exports = function () { // Use API of chosen timer. if (timer.unit == 'ns') { _.assign(templateData, { - 'begin': interpolate('s#=n#()'), - 'end': interpolate('r#=n#(s#);r#=r#[0]+(r#[1]/1e9)'), + begin: interpolate('s#=n#()'), + end: interpolate('r#=n#(s#);r#=r#[0]+(r#[1]/1e9)'), }); } else if (timer.unit == 'us') { if (timer.ns.stop) { _.assign(templateData, { - 'begin': interpolate('s#=n#.start()'), - 'end': interpolate('r#=n#.microseconds()/1e6'), + begin: interpolate('s#=n#.start()'), + end: interpolate('r#=n#.microseconds()/1e6'), }); } else { _.assign(templateData, { - 'begin': interpolate('s#=n#()'), - 'end': interpolate('r#=(n#()-s#)/1e6'), + begin: interpolate('s#=n#()'), + end: interpolate('r#=(n#()-s#)/1e6'), }); } } else if (timer.ns.now) { _.assign(templateData, { - 'begin': interpolate('s#=n#.now()'), - 'end': interpolate('r#=(n#.now()-s#)/1e3'), + begin: interpolate('s#=n#.now()'), + end: interpolate('r#=(n#.now()-s#)/1e3'), }); } else { _.assign(templateData, { - 'begin': interpolate('s#=new n#().getTime()'), - 'end': interpolate('r#=(new n#().getTime()-s#)/1e3'), + begin: interpolate('s#=new n#().getTime()'), + end: interpolate('r#=(new n#().getTime()-s#)/1e3'), }); } // Define `timer` methods. timer.start = createFunction( interpolate('o#'), - interpolate('var n#=this.ns,${begin};o#.elapsed=0;o#.timeStamp=s#') + interpolate('var n#=this.ns,${begin};o#.elapsed=0;o#.timeStamp=s#'), ); timer.stop = createFunction( interpolate('o#'), - interpolate('var n#=this.ns,s#=o#.timeStamp,${end};o#.elapsed=r#') + interpolate('var n#=this.ns,s#=o#.timeStamp,${end};o#.elapsed=r#'), ); // Create compiled test. return createFunction( interpolate('window,t#'), 'var global = window, clearTimeout = global.clearTimeout, setTimeout = global.setTimeout;\n' + - interpolate(body) + interpolate(body), ); } @@ -1947,7 +1945,7 @@ module.exports = function () { function interpolate(string) { // Replaces all occurrences of `#` with a unique number and template tokens with content. return _.template(string.replace(/\#/g, /\d+/.exec(templateData.uid)))( - templateData + templateData, ); } @@ -1958,7 +1956,7 @@ module.exports = function () { // line switch in at least Chrome 7 to use chrome.Interval try { if ((timer.ns = new (context.chrome || context.chromium).Interval())) { - timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' }); + timers.push({ ns: timer.ns, res: getRes('us'), unit: 'us' }); } } catch (e) {} @@ -1967,7 +1965,7 @@ module.exports = function () { processObject && typeof (timer.ns = processObject.hrtime) == 'function' ) { - timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' }); + timers.push({ ns: timer.ns, res: getRes('ns'), unit: 'ns' }); } // Pick timer with highest resolution. timer = _.minBy(timers, 'res'); @@ -2007,14 +2005,14 @@ module.exports = function () { function enqueue() { queue.push( bench.clone({ - '_original': bench, - 'events': { - 'abort': [update], - 'cycle': [update], - 'error': [update], - 'start': [update], + _original: bench, + events: { + abort: [update], + cycle: [update], + error: [update], + start: [update], }, - }) + }), ); } @@ -2096,12 +2094,12 @@ module.exports = function () { rme = (moe / mean) * 100 || 0; _.assign(bench.stats, { - 'deviation': sd, - 'mean': mean, - 'moe': moe, - 'rme': rme, - 'sem': sem, - 'variance': variance, + deviation: sd, + mean: mean, + moe: moe, + rme: rme, + sem: sem, + variance: variance, }); // Abort the cycle loop when the minimum sample size has been collected @@ -2133,11 +2131,11 @@ module.exports = function () { // Init queue and begin. enqueue(); invoke(queue, { - 'name': 'run', - 'args': { 'async': async }, - 'queued': true, - 'onCycle': evaluate, - 'onComplete': function () { + name: 'run', + args: { async: async }, + queued: true, + onCycle: evaluate, + onComplete: function () { bench.emit('complete'); }, }); @@ -2277,7 +2275,7 @@ module.exports = function () { if (!event.cancelled) { options = { - 'async': + async: ((options = options && options.async) == null ? bench.async : options) && support.timeout, @@ -2315,7 +2313,7 @@ module.exports = function () { * @memberOf Benchmark * @type Object */ - 'options': { + options: { /** * A flag to indicate that benchmark cycles will execute asynchronously * by default. @@ -2323,7 +2321,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type boolean */ - 'async': false, + async: false, /** * A flag to indicate that the benchmark clock is deferred. @@ -2331,14 +2329,14 @@ module.exports = function () { * @memberOf Benchmark.options * @type boolean */ - 'defer': false, + defer: false, /** * The delay between test cycles (secs). * @memberOf Benchmark.options * @type number */ - 'delay': 0.005, + delay: 0.005, /** * Displayed by `Benchmark#toString` when a `name` is not available @@ -2347,7 +2345,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type string */ - 'id': undefined, + id: undefined, /** * The default number of times to execute a test on a benchmark's first cycle. @@ -2355,7 +2353,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type number */ - 'initCount': 1, + initCount: 1, /** * The maximum time a benchmark is allowed to run before finishing (secs). @@ -2365,7 +2363,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type number */ - 'maxTime': 5, + maxTime: 5, /** * The minimum sample size required to perform statistical analysis. @@ -2373,7 +2371,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type number */ - 'minSamples': 5, + minSamples: 5, /** * The time needed to reduce the percent uncertainty of measurement to 1% (secs). @@ -2381,7 +2379,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type number */ - 'minTime': 0, + minTime: 0, /** * The name of the benchmark. @@ -2389,7 +2387,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type string */ - 'name': undefined, + name: undefined, /** * An event listener called when the benchmark is aborted. @@ -2397,7 +2395,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type Function */ - 'onAbort': undefined, + onAbort: undefined, /** * An event listener called when the benchmark completes running. @@ -2405,7 +2403,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type Function */ - 'onComplete': undefined, + onComplete: undefined, /** * An event listener called after each run cycle. @@ -2413,7 +2411,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type Function */ - 'onCycle': undefined, + onCycle: undefined, /** * An event listener called when a test errors. @@ -2421,7 +2419,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type Function */ - 'onError': undefined, + onError: undefined, /** * An event listener called when the benchmark is reset. @@ -2429,7 +2427,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type Function */ - 'onReset': undefined, + onReset: undefined, /** * An event listener called when the benchmark starts running. @@ -2437,7 +2435,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type Function */ - 'onStart': undefined, + onStart: undefined, }, /** @@ -2448,18 +2446,18 @@ module.exports = function () { * @memberOf Benchmark * @type Object */ - 'platform': context.platform || + platform: context.platform || require('platform') || { - 'description': + description: (context.navigator && context.navigator.userAgent) || null, - 'layout': null, - 'product': null, - 'name': null, - 'manufacturer': null, - 'os': null, - 'prerelease': null, - 'version': null, - 'toString': function () { + layout: null, + product: null, + name: null, + manufacturer: null, + os: null, + prerelease: null, + version: null, + toString: function () { return this.description || ''; }, }, @@ -2471,16 +2469,16 @@ module.exports = function () { * @memberOf Benchmark * @type string */ - 'version': '2.1.2', + version: '2.1.2', }); _.assign(Benchmark, { - 'filter': filter, - 'formatNumber': formatNumber, - 'invoke': invoke, - 'join': join, - 'runInContext': runInContext, - 'support': support, + filter: filter, + formatNumber: formatNumber, + invoke: invoke, + join: join, + runInContext: runInContext, + support: support, }); // Add lodash methods to Benchmark. @@ -2488,7 +2486,7 @@ module.exports = function () { ['each', 'forEach', 'forOwn', 'has', 'indexOf', 'map', 'reduce'], function (methodName) { Benchmark[methodName] = _[methodName]; - } + }, ); /*------------------------------------------------------------------------*/ @@ -2500,7 +2498,7 @@ module.exports = function () { * @memberOf Benchmark * @type number */ - 'count': 0, + count: 0, /** * The number of cycles performed while benchmarking. @@ -2508,7 +2506,7 @@ module.exports = function () { * @memberOf Benchmark * @type number */ - 'cycles': 0, + cycles: 0, /** * The number of executions per second. @@ -2516,7 +2514,7 @@ module.exports = function () { * @memberOf Benchmark * @type number */ - 'hz': 0, + hz: 0, /** * The compiled test function. @@ -2524,7 +2522,7 @@ module.exports = function () { * @memberOf Benchmark * @type {Function|string} */ - 'compiled': undefined, + compiled: undefined, /** * The error object if the test failed. @@ -2532,7 +2530,7 @@ module.exports = function () { * @memberOf Benchmark * @type Object */ - 'error': undefined, + error: undefined, /** * The test to benchmark. @@ -2540,7 +2538,7 @@ module.exports = function () { * @memberOf Benchmark * @type {Function|string} */ - 'fn': undefined, + fn: undefined, /** * A flag to indicate if the benchmark is aborted. @@ -2548,7 +2546,7 @@ module.exports = function () { * @memberOf Benchmark * @type boolean */ - 'aborted': false, + aborted: false, /** * A flag to indicate if the benchmark is running. @@ -2556,7 +2554,7 @@ module.exports = function () { * @memberOf Benchmark * @type boolean */ - 'running': false, + running: false, /** * Compiled into the test and executed immediately **before** the test loop. @@ -2619,7 +2617,7 @@ module.exports = function () { * }()) * }()) */ - 'setup': _.noop, + setup: _.noop, /** * Compiled into the test and executed immediately **after** the test loop. @@ -2627,7 +2625,7 @@ module.exports = function () { * @memberOf Benchmark * @type {Function|string} */ - 'teardown': _.noop, + teardown: _.noop, /** * An object of stats including mean, margin or error, and standard deviation. @@ -2635,14 +2633,14 @@ module.exports = function () { * @memberOf Benchmark * @type Object */ - 'stats': { + stats: { /** * The margin of error. * * @memberOf Benchmark#stats * @type number */ - 'moe': 0, + moe: 0, /** * The relative margin of error (expressed as a percentage of the mean). @@ -2650,7 +2648,7 @@ module.exports = function () { * @memberOf Benchmark#stats * @type number */ - 'rme': 0, + rme: 0, /** * The standard error of the mean. @@ -2658,7 +2656,7 @@ module.exports = function () { * @memberOf Benchmark#stats * @type number */ - 'sem': 0, + sem: 0, /** * The sample standard deviation. @@ -2666,7 +2664,7 @@ module.exports = function () { * @memberOf Benchmark#stats * @type number */ - 'deviation': 0, + deviation: 0, /** * The sample arithmetic mean (secs). @@ -2674,7 +2672,7 @@ module.exports = function () { * @memberOf Benchmark#stats * @type number */ - 'mean': 0, + mean: 0, /** * The array of sampled periods. @@ -2682,7 +2680,7 @@ module.exports = function () { * @memberOf Benchmark#stats * @type Array */ - 'sample': [], + sample: [], /** * The sample variance. @@ -2690,7 +2688,7 @@ module.exports = function () { * @memberOf Benchmark#stats * @type number */ - 'variance': 0, + variance: 0, }, /** @@ -2699,14 +2697,14 @@ module.exports = function () { * @memberOf Benchmark * @type Object */ - 'times': { + times: { /** * The time taken to complete the last cycle (secs). * * @memberOf Benchmark#times * @type number */ - 'cycle': 0, + cycle: 0, /** * The time taken to complete the benchmark (secs). @@ -2714,7 +2712,7 @@ module.exports = function () { * @memberOf Benchmark#times * @type number */ - 'elapsed': 0, + elapsed: 0, /** * The time taken to execute the test once (secs). @@ -2722,7 +2720,7 @@ module.exports = function () { * @memberOf Benchmark#times * @type number */ - 'period': 0, + period: 0, /** * A timestamp of when the benchmark started (ms). @@ -2730,21 +2728,21 @@ module.exports = function () { * @memberOf Benchmark#times * @type number */ - 'timeStamp': 0, + timeStamp: 0, }, }); _.assign(Benchmark.prototype, { - 'abort': abort, - 'clone': clone, - 'compare': compare, - 'emit': emit, - 'listeners': listeners, - 'off': off, - 'on': on, - 'reset': reset, - 'run': run, - 'toString': toStringBench, + abort: abort, + clone: clone, + compare: compare, + emit: emit, + listeners: listeners, + off: off, + on: on, + reset: reset, + run: run, + toString: toStringBench, }); /*------------------------------------------------------------------------*/ @@ -2756,7 +2754,7 @@ module.exports = function () { * @memberOf Benchmark.Deferred * @type Object */ - 'benchmark': null, + benchmark: null, /** * The number of deferred cycles performed while benchmarking. @@ -2764,7 +2762,7 @@ module.exports = function () { * @memberOf Benchmark.Deferred * @type number */ - 'cycles': 0, + cycles: 0, /** * The time taken to complete the deferred benchmark (secs). @@ -2772,7 +2770,7 @@ module.exports = function () { * @memberOf Benchmark.Deferred * @type number */ - 'elapsed': 0, + elapsed: 0, /** * A timestamp of when the deferred benchmark started (ms). @@ -2780,11 +2778,11 @@ module.exports = function () { * @memberOf Benchmark.Deferred * @type number */ - 'timeStamp': 0, + timeStamp: 0, }); _.assign(Deferred.prototype, { - 'resolve': resolve, + resolve: resolve, }); /*------------------------------------------------------------------------*/ @@ -2796,7 +2794,7 @@ module.exports = function () { * @memberOf Benchmark.Event * @type boolean */ - 'aborted': false, + aborted: false, /** * A flag to indicate if the default action is cancelled. @@ -2804,7 +2802,7 @@ module.exports = function () { * @memberOf Benchmark.Event * @type boolean */ - 'cancelled': false, + cancelled: false, /** * The object whose listeners are currently being processed. @@ -2812,7 +2810,7 @@ module.exports = function () { * @memberOf Benchmark.Event * @type Object */ - 'currentTarget': undefined, + currentTarget: undefined, /** * The return value of the last executed listener. @@ -2820,7 +2818,7 @@ module.exports = function () { * @memberOf Benchmark.Event * @type Mixed */ - 'result': undefined, + result: undefined, /** * The object to which the event was originally emitted. @@ -2828,7 +2826,7 @@ module.exports = function () { * @memberOf Benchmark.Event * @type Object */ - 'target': undefined, + target: undefined, /** * A timestamp of when the event was created (ms). @@ -2836,7 +2834,7 @@ module.exports = function () { * @memberOf Benchmark.Event * @type number */ - 'timeStamp': 0, + timeStamp: 0, /** * The event type. @@ -2844,7 +2842,7 @@ module.exports = function () { * @memberOf Benchmark.Event * @type string */ - 'type': '', + type: '', }); /*------------------------------------------------------------------------*/ @@ -2863,7 +2861,7 @@ module.exports = function () { * @memberOf Benchmark.Suite.options * @type string */ - 'name': undefined, + name: undefined, }; /*------------------------------------------------------------------------*/ @@ -2875,7 +2873,7 @@ module.exports = function () { * @memberOf Benchmark.Suite * @type number */ - 'length': 0, + length: 0, /** * A flag to indicate if the suite is aborted. @@ -2883,7 +2881,7 @@ module.exports = function () { * @memberOf Benchmark.Suite * @type boolean */ - 'aborted': false, + aborted: false, /** * A flag to indicate if the suite is running. @@ -2891,38 +2889,38 @@ module.exports = function () { * @memberOf Benchmark.Suite * @type boolean */ - 'running': false, + running: false, }); _.assign(Suite.prototype, { - 'abort': abortSuite, - 'add': add, - 'clone': cloneSuite, - 'emit': emit, - 'filter': filterSuite, - 'join': arrayRef.join, - 'listeners': listeners, - 'off': off, - 'on': on, - 'pop': arrayRef.pop, - 'push': push, - 'reset': resetSuite, - 'run': runSuite, - 'reverse': arrayRef.reverse, - 'shift': shift, - 'slice': slice, - 'sort': arrayRef.sort, - 'splice': arrayRef.splice, - 'unshift': unshift, + abort: abortSuite, + add: add, + clone: cloneSuite, + emit: emit, + filter: filterSuite, + join: arrayRef.join, + listeners: listeners, + off: off, + on: on, + pop: arrayRef.pop, + push: push, + reset: resetSuite, + run: runSuite, + reverse: arrayRef.reverse, + shift: shift, + slice: slice, + sort: arrayRef.sort, + splice: arrayRef.splice, + unshift: unshift, }); /*------------------------------------------------------------------------*/ // Expose Deferred, Event, and Suite. _.assign(Benchmark, { - 'Deferred': Deferred, - 'Event': Event, - 'Suite': Suite, + Deferred: Deferred, + Event: Event, + Suite: Suite, }); /*------------------------------------------------------------------------*/ @@ -2937,7 +2935,7 @@ module.exports = function () { push.apply(args, arguments); return func.apply(_, args); }; - } + }, ); // Avoid array-like object bugs with `Array#shift` and `Array#splice` diff --git a/tgui/packages/tgui-bench/package.json b/tgui/packages/tgui-bench/package.json index 6e4083ba63..47cad082fb 100644 --- a/tgui/packages/tgui-bench/package.json +++ b/tgui/packages/tgui-bench/package.json @@ -1,15 +1,14 @@ { "private": true, "name": "tgui-bench", - "version": "4.3.0", + "version": "5.0.0", "dependencies": { - "@fastify/static": "^5.0.2", + "@fastify/static": "^6.12.0", "common": "workspace:*", - "fastify": "^3.29.4", - "inferno": "^7.4.8", - "inferno-vnode-flags": "^7.4.8", + "fastify": "^3.29.5", "lodash": "^4.17.21", "platform": "^1.3.6", + "react": "^18.2.0", "tgui": "workspace:*" } } diff --git a/tgui/packages/tgui-bench/tests/Button.test.tsx b/tgui/packages/tgui-bench/tests/Button.test.tsx index 6b806d720a..0549e69b62 100644 --- a/tgui/packages/tgui-bench/tests/Button.test.tsx +++ b/tgui/packages/tgui-bench/tests/Button.test.tsx @@ -1,11 +1,8 @@ -import { linkEvent } from 'inferno'; import { Button } from 'tgui/components'; import { createRenderer } from 'tgui/renderer'; const render = createRenderer(); -const handleClick = () => undefined; - export const SingleButton = () => { const node = ; render(node); @@ -16,13 +13,6 @@ export const SingleButtonWithCallback = () => { render(node); }; -export const SingleButtonWithLinkEvent = () => { - const node = ( - - ); - render(node); -}; - export const ListOfButtons = () => { const nodes: JSX.Element[] = []; for (let i = 0; i < 100; i++) { @@ -45,19 +35,6 @@ export const ListOfButtonsWithCallback = () => { render(
{nodes}
); }; -export const ListOfButtonsWithLinkEvent = () => { - const nodes: JSX.Element[] = []; - for (let i = 0; i < 100; i++) { - const node = ( - - ); - nodes.push(node); - } - render(
{nodes}
); -}; - export const ListOfButtonsWithIcons = () => { const nodes: JSX.Element[] = []; for (let i = 0; i < 100; i++) { diff --git a/tgui/packages/tgui-bench/tests/DisposalBin.test.tsx b/tgui/packages/tgui-bench/tests/DisposalBin.test.tsx index 5ac9965c76..63093f42f7 100644 --- a/tgui/packages/tgui-bench/tests/DisposalBin.test.tsx +++ b/tgui/packages/tgui-bench/tests/DisposalBin.test.tsx @@ -1,22 +1,19 @@ -import { StoreProvider, configureStore } from 'tgui/store'; - +import { backendUpdate, setGlobalStore } from 'tgui/backend'; import { DisposalBin } from 'tgui/interfaces/DisposalBin'; -import { backendUpdate } from 'tgui/backend'; import { createRenderer } from 'tgui/renderer'; +import { configureStore } from 'tgui/store'; const store = configureStore({ sideEffects: false }); const renderUi = createRenderer((dataJson: string) => { + setGlobalStore(store); + store.dispatch( backendUpdate({ data: Byond.parseJson(dataJson), - }) - ); - return ( - - - + }), ); + return ; }); export const data = JSON.stringify({ diff --git a/tgui/packages/tgui-bench/tests/Tooltip.test.tsx b/tgui/packages/tgui-bench/tests/Tooltip.test.tsx index ea43a61f0b..9dae16f5c0 100644 --- a/tgui/packages/tgui-bench/tests/Tooltip.test.tsx +++ b/tgui/packages/tgui-bench/tests/Tooltip.test.tsx @@ -12,7 +12,7 @@ export const ListOfTooltips = () => { Tooltip #{i} - + , ); } diff --git a/tgui/packages/tgui-dev-server/dreamseeker.js b/tgui/packages/tgui-dev-server/dreamseeker.js index c81f51e35c..d1ca2a9ac5 100644 --- a/tgui/packages/tgui-dev-server/dreamseeker.js +++ b/tgui/packages/tgui-dev-server/dreamseeker.js @@ -6,8 +6,9 @@ import { exec } from 'child_process'; import { promisify } from 'util'; -import { createLogger } from './logging'; -import { require } from './require'; + +import { createLogger } from './logging.js'; +import { require } from './require.js'; const axios = require('axios'); const logger = createLogger('dreamseeker'); @@ -30,7 +31,7 @@ export class DreamSeeker { + '=' + encodeURIComponent(params[key])) .join('&'); logger.log( - `topic call at ${this.client.defaults.baseURL + '/dummy?' + query}` + `topic call at ${this.client.defaults.baseURL + '/dummy?' + query}`, ); return this.client.get('/dummy?' + query); } diff --git a/tgui/packages/tgui-dev-server/index.js b/tgui/packages/tgui-dev-server/index.js index 460b15d99a..85489ebb04 100644 --- a/tgui/packages/tgui-dev-server/index.js +++ b/tgui/packages/tgui-dev-server/index.js @@ -4,8 +4,8 @@ * @license MIT */ -import { createCompiler } from './webpack'; -import { reloadByondCache } from './reloader'; +import { reloadByondCache } from './reloader.js'; +import { createCompiler } from './webpack.js'; const noHot = process.argv.includes('--no-hot'); const noTmp = process.argv.includes('--no-tmp'); diff --git a/tgui/packages/tgui-dev-server/link/client.cjs b/tgui/packages/tgui-dev-server/link/client.cjs index 1e21d42ce8..b0e6f7bc9d 100644 --- a/tgui/packages/tgui-dev-server/link/client.cjs +++ b/tgui/packages/tgui-dev-server/link/client.cjs @@ -31,11 +31,9 @@ const ensureConnection = () => { }; } } -}; -if (process.env.NODE_ENV !== 'production') { window.onunload = () => socket && socket.close(); -} +}; const subscribe = (fn) => subscribers.push(fn); @@ -136,38 +134,38 @@ const sendLogEntry = (level, ns, ...args) => { const setupHotReloading = () => { if ( - // prettier-ignore - process.env.NODE_ENV !== 'production' - && process.env.WEBPACK_HMR_ENABLED - && window.WebSocket + process.env.NODE_ENV === 'production' || + !process.env.WEBPACK_HMR_ENABLED || + !window.WebSocket ) { - if (module.hot) { - ensureConnection(); - sendLogEntry(0, null, 'setting up hot reloading'); - subscribe((msg) => { - const { type } = msg; - sendLogEntry(0, null, 'received', type); - if (type === 'hotUpdate') { - const status = module.hot.status(); - if (status !== 'idle') { - sendLogEntry(0, null, 'hot reload status:', status); - return; - } - module.hot - .check({ - ignoreUnaccepted: true, - ignoreDeclined: true, - ignoreErrored: true, - }) - .then((modules) => { - sendLogEntry(0, null, 'outdated modules', modules); - }) - .catch((err) => { - sendLogEntry(0, null, 'reload error', err); - }); + return; + } + if (module.hot) { + ensureConnection(); + sendLogEntry(0, null, 'setting up hot reloading'); + subscribe((msg) => { + const { type } = msg; + sendLogEntry(0, null, 'received', type); + if (type === 'hotUpdate') { + const status = module.hot.status(); + if (status !== 'idle') { + sendLogEntry(0, null, 'hot reload status:', status); + return; } - }); - } + module.hot + .check({ + ignoreUnaccepted: true, + ignoreDeclined: true, + ignoreErrored: true, + }) + .then((modules) => { + sendLogEntry(0, null, 'outdated modules', modules); + }) + .catch((err) => { + sendLogEntry(0, null, 'reload error', err); + }); + } + }); } }; diff --git a/tgui/packages/tgui-dev-server/link/retrace.js b/tgui/packages/tgui-dev-server/link/retrace.js index 949835c700..083ddb37d1 100644 --- a/tgui/packages/tgui-dev-server/link/retrace.js +++ b/tgui/packages/tgui-dev-server/link/retrace.js @@ -6,9 +6,10 @@ import fs from 'fs'; import { basename } from 'path'; -import { createLogger } from '../logging'; -import { require } from '../require'; -import { resolveGlob } from '../util'; + +import { createLogger } from '../logging.js'; +import { require } from '../require.js'; +import { resolveGlob } from '../util.js'; const SourceMap = require('source-map'); const { parse: parseStackTrace } = require('stacktrace-parser'); @@ -30,7 +31,7 @@ export const loadSourceMaps = async (bundleDir) => { try { const file = basename(path).replace('.map', ''); const consumer = await new SourceMapConsumer( - JSON.parse(fs.readFileSync(path, 'utf8')) + JSON.parse(fs.readFileSync(path, 'utf8')), ); sourceMaps.push({ file, consumer }); } catch (err) { diff --git a/tgui/packages/tgui-dev-server/link/server.js b/tgui/packages/tgui-dev-server/link/server.js index f0c0d153d3..2a1f551bf6 100644 --- a/tgui/packages/tgui-dev-server/link/server.js +++ b/tgui/packages/tgui-dev-server/link/server.js @@ -6,9 +6,10 @@ import http from 'http'; import { inspect } from 'util'; -import { createLogger, directLog } from '../logging'; -import { require } from '../require'; -import { loadSourceMaps, retrace } from './retrace'; + +import { createLogger, directLog } from '../logging.js'; +import { require } from '../require.js'; +import { loadSourceMaps, retrace } from './retrace.js'; const WebSocket = require('ws'); diff --git a/tgui/packages/tgui-dev-server/package.json b/tgui/packages/tgui-dev-server/package.json index ab45774318..496e25c6c1 100644 --- a/tgui/packages/tgui-dev-server/package.json +++ b/tgui/packages/tgui-dev-server/package.json @@ -1,13 +1,13 @@ { "private": true, "name": "tgui-dev-server", - "version": "4.4.1", + "version": "5.0.0", "type": "module", "dependencies": { - "axios": "^1.6.0", - "glob": "^8.0.3", - "source-map": "^0.7.3", + "axios": "^1.6.2", + "glob": "^7.2.0", + "source-map": "^0.7.4", "stacktrace-parser": "^0.1.10", - "ws": "^8.6.0" + "ws": "^8.14.2" } } diff --git a/tgui/packages/tgui-dev-server/reloader.js b/tgui/packages/tgui-dev-server/reloader.js index aed9a7dcd7..cb477a6523 100644 --- a/tgui/packages/tgui-dev-server/reloader.js +++ b/tgui/packages/tgui-dev-server/reloader.js @@ -7,10 +7,11 @@ import fs from 'fs'; import os from 'os'; import { basename } from 'path'; -import { DreamSeeker } from './dreamseeker'; -import { createLogger } from './logging'; -import { resolveGlob, resolvePath } from './util'; -import { regQuery } from './winreg'; + +import { DreamSeeker } from './dreamseeker.js'; +import { createLogger } from './logging.js'; +import { resolveGlob, resolvePath } from './util.js'; +import { regQuery } from './winreg.js'; const logger = createLogger('reloader'); @@ -83,19 +84,19 @@ export const reloadByondCache = async (bundleDir) => { } // Get dreamseeker instances const pids = cacheDirs.map((cacheDir) => - parseInt(cacheDir.split('/cache/tmp').pop(), 10) + parseInt(cacheDir.split('/cache/tmp').pop(), 10), ); const dssPromise = DreamSeeker.getInstancesByPids(pids); // Copy assets const assets = await resolveGlob( bundleDir, - './*.+(bundle|chunk|hot-update).*' + './*.+(bundle|chunk|hot-update).*', ); for (let cacheDir of cacheDirs) { // Clear garbage const garbage = await resolveGlob( cacheDir, - './*.+(bundle|chunk|hot-update).*' + './*.+(bundle|chunk|hot-update).*', ); try { // Plant a dummy browser window file, we'll be using this to avoid world topic. For byond 515. diff --git a/tgui/packages/tgui-dev-server/util.js b/tgui/packages/tgui-dev-server/util.js index d60ebb212f..79190fe189 100644 --- a/tgui/packages/tgui-dev-server/util.js +++ b/tgui/packages/tgui-dev-server/util.js @@ -6,7 +6,8 @@ import fs from 'fs'; import path from 'path'; -import { require } from './require'; + +import { require } from './require.js'; const globPkg = require('glob'); diff --git a/tgui/packages/tgui-dev-server/webpack.js b/tgui/packages/tgui-dev-server/webpack.js index 1c16345a89..e4fbdeb9f1 100644 --- a/tgui/packages/tgui-dev-server/webpack.js +++ b/tgui/packages/tgui-dev-server/webpack.js @@ -7,10 +7,11 @@ import fs from 'fs'; import { createRequire } from 'module'; import { dirname } from 'path'; -import { loadSourceMaps, setupLink } from './link/server'; -import { createLogger } from './logging'; -import { reloadByondCache } from './reloader'; -import { resolveGlob } from './util'; + +import { loadSourceMaps, setupLink } from './link/server.js'; +import { createLogger } from './logging.js'; +import { reloadByondCache } from './reloader.js'; +import { resolveGlob } from './util.js'; const logger = createLogger('webpack'); diff --git a/tgui/packages/tgui-dev-server/winreg.js b/tgui/packages/tgui-dev-server/winreg.js index d7408b5c39..43a4170190 100644 --- a/tgui/packages/tgui-dev-server/winreg.js +++ b/tgui/packages/tgui-dev-server/winreg.js @@ -8,7 +8,8 @@ import { exec } from 'child_process'; import { promisify } from 'util'; -import { createLogger } from './logging'; + +import { createLogger } from './logging.js'; const logger = createLogger('winreg'); @@ -35,8 +36,8 @@ export const regQuery = async (path, key) => { logger.error('could not find the start of the key value'); return null; } - const value = stdout.substring(indexOfValue + 4, indexOfEol); - return value; + + return stdout.substring(indexOfValue + 4, indexOfEol); } catch (err) { logger.error(err); return null; diff --git a/tgui/packages/tgui-panel/Panel.tsx b/tgui/packages/tgui-panel/Panel.tsx index 8171333866..10088e0954 100644 --- a/tgui/packages/tgui-panel/Panel.tsx +++ b/tgui/packages/tgui-panel/Panel.tsx @@ -6,6 +6,7 @@ import { Button, Section, Stack } from 'tgui/components'; import { Pane } from 'tgui/layouts'; + import { NowPlayingWidget, useAudio } from './audio'; import { ChatPanel, ChatTabs } from './chat'; import { useGame } from './game'; @@ -14,13 +15,13 @@ import { PingIndicator } from './ping'; import { ReconnectButton } from './reconnect'; import { SettingsPanel, useSettings } from './settings'; -export const Panel = (props, context) => { - const audio = useAudio(context); - const settings = useSettings(context); - const game = useGame(context); +export const Panel = (props) => { + const audio = useAudio(); + const settings = useSettings(); + const game = useGame(); if (process.env.NODE_ENV !== 'production') { const { useDebug, KitchenSink } = require('tgui/debug'); - const debug = useDebug(context); + const debug = useDebug(); if (debug.kitchenSink) { return ; } diff --git a/tgui/packages/tgui-panel/audio/NowPlayingWidget.jsx b/tgui/packages/tgui-panel/audio/NowPlayingWidget.jsx index 30052c3f3a..903dacf284 100644 --- a/tgui/packages/tgui-panel/audio/NowPlayingWidget.jsx +++ b/tgui/packages/tgui-panel/audio/NowPlayingWidget.jsx @@ -5,15 +5,15 @@ */ import { toFixed } from 'common/math'; -import { useDispatch, useSelector } from 'common/redux'; +import { useDispatch, useSelector } from 'tgui/backend'; import { Button, Collapsible, Flex, Knob, Section } from 'tgui/components'; import { useSettings } from '../settings'; import { selectAudio } from './selectors'; -export const NowPlayingWidget = (props, context) => { - const audio = useSelector(context, selectAudio), - dispatch = useDispatch(context), - settings = useSettings(context), +export const NowPlayingWidget = (props) => { + const audio = useSelector(selectAudio), + dispatch = useDispatch(), + settings = useSettings(), title = audio.meta?.title, URL = audio.meta?.link, Artist = audio.meta?.artist || 'Unknown Artist', @@ -22,10 +22,10 @@ export const NowPlayingWidget = (props, context) => { duration = audio.meta?.duration, date = !isNaN(upload_date) ? upload_date?.substring(0, 4) + - '-' + - upload_date?.substring(4, 6) + - '-' + - upload_date?.substring(6, 8) + '-' + + upload_date?.substring(4, 6) + + '-' + + upload_date?.substring(6, 8) : upload_date; return ( @@ -35,10 +35,11 @@ export const NowPlayingWidget = (props, context) => { mx={0.5} grow={1} style={{ - 'white-space': 'nowrap', - 'overflow': 'hidden', - 'text-overflow': 'ellipsis', - }}> + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }} + > {
diff --git a/tgui/packages/tgui-panel/audio/hooks.ts b/tgui/packages/tgui-panel/audio/hooks.ts index 504b4f5c6e..2e9d830cb7 100644 --- a/tgui/packages/tgui-panel/audio/hooks.ts +++ b/tgui/packages/tgui-panel/audio/hooks.ts @@ -4,12 +4,13 @@ * @license MIT */ -import { useSelector, useDispatch } from 'common/redux'; +import { useDispatch, useSelector } from 'tgui/backend'; + import { selectAudio } from './selectors'; -export const useAudio = (context) => { - const state = useSelector(context, selectAudio); - const dispatch = useDispatch(context); +export const useAudio = () => { + const state = useSelector(selectAudio); + const dispatch = useDispatch(); return { ...state, toggle: () => dispatch({ type: 'audio/toggle' }), diff --git a/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx b/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx index 2ccc491d95..85f3b278e3 100644 --- a/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx +++ b/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx @@ -4,15 +4,28 @@ * @license MIT */ -import { useDispatch, useSelector } from 'common/redux'; -import { Button, Collapsible, Divider, Input, Section, Stack } from 'tgui/components'; -import { moveChatPageLeft, moveChatPageRight, removeChatPage, toggleAcceptedType, updateChatPage } from './actions'; +import { useDispatch, useSelector } from 'tgui/backend'; +import { + Button, + Collapsible, + Divider, + Input, + Section, + Stack, +} from 'tgui/components'; +import { + moveChatPageLeft, + moveChatPageRight, + removeChatPage, + toggleAcceptedType, + updateChatPage, +} from './actions'; import { MESSAGE_TYPES } from './constants'; import { selectCurrentChatPage } from './selectors'; -export const ChatPageSettings = (props, context) => { - const page = useSelector(context, selectCurrentChatPage); - const dispatch = useDispatch(context); +export const ChatPageSettings = (props) => { + const page = useSelector(selectCurrentChatPage); + const dispatch = useDispatch(); return (
@@ -25,7 +38,7 @@ export const ChatPageSettings = (props, context) => { updateChatPage({ pageId: page.id, name: value, - }) + }), ) } /> @@ -39,9 +52,10 @@ export const ChatPageSettings = (props, context) => { dispatch( removeChatPage({ pageId: page.id, - }) + }), ) - }> + } + > Remove @@ -60,9 +74,10 @@ export const ChatPageSettings = (props, context) => { dispatch( moveChatPageLeft({ pageId: page.id, - }) + }), ) - }> + } + > « @@ -84,7 +100,7 @@ export const ChatPageSettings = (props, context) => {
{MESSAGE_TYPES.filter( - (typeDef) => !typeDef.important && !typeDef.admin + (typeDef) => !typeDef.important && !typeDef.admin, ).map((typeDef) => ( { toggleAcceptedType({ pageId: page.id, type: typeDef.type, - }) + }), ) - }> + } + > {typeDef.name} ))} {MESSAGE_TYPES.filter( - (typeDef) => !typeDef.important && typeDef.admin + (typeDef) => !typeDef.important && typeDef.admin, ).map((typeDef) => ( { toggleAcceptedType({ pageId: page.id, type: typeDef.type, - }) + }), ) - }> + } + > {typeDef.name} ))} diff --git a/tgui/packages/tgui-panel/chat/ChatPanel.jsx b/tgui/packages/tgui-panel/chat/ChatPanel.jsx index 3132a66ce7..36e86876f5 100644 --- a/tgui/packages/tgui-panel/chat/ChatPanel.jsx +++ b/tgui/packages/tgui-panel/chat/ChatPanel.jsx @@ -5,13 +5,13 @@ */ import { shallowDiffers } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { Button } from 'tgui/components'; import { chatRenderer } from './renderer'; export class ChatPanel extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.ref = createRef(); this.state = { scrollTracking: true, @@ -26,7 +26,7 @@ export class ChatPanel extends Component { chatRenderer.mount(this.ref.current); chatRenderer.events.on( 'scrollTrackingChanged', - this.handleScrollTrackingChange + this.handleScrollTrackingChange, ); this.componentDidUpdate(); } @@ -34,7 +34,7 @@ export class ChatPanel extends Component { componentWillUnmount() { chatRenderer.events.off( 'scrollTrackingChanged', - this.handleScrollTrackingChange + this.handleScrollTrackingChange, ); } @@ -46,7 +46,7 @@ export class ChatPanel extends Component { !prevProps || shallowDiffers(this.props, prevProps); if (shouldUpdateStyle) { chatRenderer.assignStyle({ - 'width': '100%', + width: '100%', 'white-space': 'pre-wrap', 'font-size': this.props.fontSize, 'line-height': this.props.lineHeight, @@ -63,7 +63,8 @@ export class ChatPanel extends Component { )} diff --git a/tgui/packages/tgui-panel/chat/ChatTabs.jsx b/tgui/packages/tgui-panel/chat/ChatTabs.jsx index 1d4f6f65ed..ed3a285c20 100644 --- a/tgui/packages/tgui-panel/chat/ChatTabs.jsx +++ b/tgui/packages/tgui-panel/chat/ChatTabs.jsx @@ -4,7 +4,7 @@ * @license MIT */ -import { useDispatch, useSelector } from 'common/redux'; +import { useDispatch, useSelector } from 'tgui/backend'; import { Box, Tabs, Flex, Button } from 'tgui/components'; import { changeChatPage, addChatPage } from './actions'; import { selectChatPages, selectCurrentChatPage } from './selectors'; @@ -13,21 +13,22 @@ import { openChatSettings } from '../settings/actions'; const UnreadCountWidget = ({ value }) => ( + fontSize: '0.7em', + borderRadius: '0.25em', + width: '1.7em', + lineHeight: '1.55em', + backgroundColor: 'crimson', + color: '#fff', + }} + > {Math.min(value, 99)} ); -export const ChatTabs = (props, context) => { - const pages = useSelector(context, selectChatPages); - const currentPage = useSelector(context, selectCurrentChatPage); - const dispatch = useDispatch(context); +export const ChatTabs = (props) => { + const pages = useSelector(selectChatPages); + const currentPage = useSelector(selectCurrentChatPage); + const dispatch = useDispatch(); return ( @@ -45,9 +46,10 @@ export const ChatTabs = (props, context) => { dispatch( changeChatPage({ pageId: page.id, - }) + }), ) - }> + } + > {page.name} ))} diff --git a/tgui/packages/tgui-panel/chat/actions.js b/tgui/packages/tgui-panel/chat/actions.js index 37fb48cc81..0f87a8b0e2 100644 --- a/tgui/packages/tgui-panel/chat/actions.js +++ b/tgui/packages/tgui-panel/chat/actions.js @@ -5,6 +5,7 @@ */ import { createAction } from 'common/redux'; + import { createPage } from './model'; export const loadChat = createAction('chat/load'); diff --git a/tgui/packages/tgui-panel/chat/middleware.js b/tgui/packages/tgui-panel/chat/middleware.js index 7c90df4410..ca04222463 100644 --- a/tgui/packages/tgui-panel/chat/middleware.js +++ b/tgui/packages/tgui-panel/chat/middleware.js @@ -4,11 +4,31 @@ * @license MIT */ -import DOMPurify from 'dompurify'; import { storage } from 'common/storage'; -import { loadSettings, updateSettings, addHighlightSetting, removeHighlightSetting, updateHighlightSetting } from '../settings/actions'; +import DOMPurify from 'dompurify'; + +import { + addHighlightSetting, + loadSettings, + removeHighlightSetting, + updateHighlightSetting, + updateSettings, +} from '../settings/actions'; import { selectSettings } from '../settings/selectors'; -import { addChatPage, changeChatPage, changeScrollTracking, loadChat, rebuildChat, moveChatPageLeft, moveChatPageRight, removeChatPage, saveChatToDisk, purgeChatMessageArchive, toggleAcceptedType, updateMessageCount } from './actions'; +import { + addChatPage, + changeChatPage, + changeScrollTracking, + loadChat, + moveChatPageLeft, + moveChatPageRight, + purgeChatMessageArchive, + rebuildChat, + removeChatPage, + saveChatToDisk, + toggleAcceptedType, + updateMessageCount, +} from './actions'; import { MESSAGE_SAVE_INTERVAL } from './constants'; import { createMessage, serializeMessage } from './model'; import { chatRenderer } from './renderer'; @@ -22,7 +42,7 @@ const saveChatToStorage = async (store) => { const settings = selectSettings(store.getState()); const fromIndex = Math.max( 0, - chatRenderer.messages.length - settings.persistentMessageLimit + chatRenderer.messages.length - settings.persistentMessageLimit, ); const messages = chatRenderer.messages .slice(fromIndex) @@ -31,7 +51,7 @@ const saveChatToStorage = async (store) => { storage.set('chat-messages', messages); storage.set( 'chat-messages-archive', - chatRenderer.archivedMessages.map((message) => serializeMessage(message)) + chatRenderer.archivedMessages.map((message) => serializeMessage(message)), ); // FIXME: Better chat history }; @@ -122,7 +142,7 @@ export const chatMiddleware = (store) => { settings.visibleMessageLimit, settings.combineMessageLimit, settings.combineIntervalLimit, - settings.logLineCount + settings.logLineCount, ); if (!initialized) { initialized = true; @@ -204,7 +224,7 @@ export const chatMiddleware = (store) => { next(action); chatRenderer.setHighlight( settings.highlightSettings, - settings.highlightSettingById + settings.highlightSettingById, ); return; diff --git a/tgui/packages/tgui-panel/chat/model.js b/tgui/packages/tgui-panel/chat/model.js index e544ebfddd..5f8a96227d 100644 --- a/tgui/packages/tgui-panel/chat/model.js +++ b/tgui/packages/tgui-panel/chat/model.js @@ -5,7 +5,8 @@ */ import { createUuid } from 'common/uuid'; -import { MESSAGE_TYPES, MESSAGE_TYPE_INTERNAL } from './constants'; + +import { MESSAGE_TYPE_INTERNAL, MESSAGE_TYPES } from './constants'; export const canPageAcceptType = (page, type) => type.startsWith(MESSAGE_TYPE_INTERNAL) || page.acceptedTypes[type]; diff --git a/tgui/packages/tgui-panel/chat/reducer.js b/tgui/packages/tgui-panel/chat/reducer.js index 488bd99320..effc942395 100644 --- a/tgui/packages/tgui-panel/chat/reducer.js +++ b/tgui/packages/tgui-panel/chat/reducer.js @@ -4,7 +4,18 @@ * @license MIT */ -import { addChatPage, changeChatPage, loadChat, removeChatPage, moveChatPageLeft, moveChatPageRight, toggleAcceptedType, updateChatPage, updateMessageCount, changeScrollTracking } from './actions'; +import { + addChatPage, + changeChatPage, + changeScrollTracking, + loadChat, + moveChatPageLeft, + moveChatPageRight, + removeChatPage, + toggleAcceptedType, + updateChatPage, + updateMessageCount, +} from './actions'; import { canPageAcceptType, createMainPage } from './model'; const mainPage = createMainPage(); diff --git a/tgui/packages/tgui-panel/chat/renderer.jsx b/tgui/packages/tgui-panel/chat/renderer.jsx index 3b060548bb..a3513fb6c4 100644 --- a/tgui/packages/tgui-panel/chat/renderer.jsx +++ b/tgui/packages/tgui-panel/chat/renderer.jsx @@ -7,9 +7,22 @@ import { EventEmitter } from 'common/events'; import { classes } from 'common/react'; import { createLogger } from 'tgui/logging'; -import { IMAGE_RETRY_DELAY, IMAGE_RETRY_LIMIT, IMAGE_RETRY_MESSAGE_AGE, MESSAGE_PRUNE_INTERVAL, MESSAGE_TYPES, MESSAGE_TYPE_INTERNAL, MESSAGE_TYPE_UNKNOWN } from './constants'; -import { render } from 'inferno'; -import { canPageAcceptType, createMessage, isSameMessage, serializeMessage } from './model'; +import { + IMAGE_RETRY_DELAY, + IMAGE_RETRY_LIMIT, + IMAGE_RETRY_MESSAGE_AGE, + MESSAGE_PRUNE_INTERVAL, + MESSAGE_TYPES, + MESSAGE_TYPE_INTERNAL, + MESSAGE_TYPE_UNKNOWN, +} from './constants'; +import { render } from 'react-dom'; +import { + canPageAcceptType, + createMessage, + isSameMessage, + serializeMessage, +} from './model'; import { highlightNode, linkifyNode } from './replaceInTextNode'; import { Tooltip } from '../../tgui/components'; @@ -27,8 +40,8 @@ export const TGUI_CHAT_COMPONENTS = { // List of injectable attibute names mapped to their proper prop // We need this because attibutes don't support lowercase names export const TGUI_CHAT_ATTRIBUTES_TO_PROPS = { - 'position': 'position', - 'content': 'content', + position: 'position', + content: 'content', }; const findNearestScrollableParent = (startingNode) => { @@ -213,7 +226,7 @@ class ChatRenderer { // Must be alphanumeric (with some punctuation) allowedRegex.test(str) && // Reset lastIndex so it does not mess up the next word - ((allowedRegex.lastIndex = 0) || true) + ((allowedRegex.lastIndex = 0) || true), ); let highlightWords; let highlightRegex; @@ -232,7 +245,7 @@ class ChatRenderer { // Must be alphanumeric (with some punctuation) allowedRegex.test(str) && // Reset lastIndex so it does not mess up the next word - ((allowedRegex.lastIndex = 0) || true) + ((allowedRegex.lastIndex = 0) || true), ); let blacklistWords; let blacklistregex; @@ -302,7 +315,7 @@ class ChatRenderer { highlightRegex = new RegExp('(' + regexStr + ')', flags); } else { const pattern = `${matchWord ? '\\b' : ''}(${highlightWords.join( - '|' + '|', )})${matchWord ? '\\b' : ''}`; highlightRegex = new RegExp(pattern, flags); } @@ -361,7 +374,7 @@ class ChatRenderer { visibleMessageLimit, combineMessageLimit, combineIntervalLimit, - exportLimit + exportLimit, ) { this.visibleMessageLimit = visibleMessageLimit; this.combineMessageLimit = combineMessageLimit; @@ -485,7 +498,7 @@ class ChatRenderer { , - childNode + childNode, ); /* eslint-enable react/no-danger */ } @@ -504,7 +517,7 @@ class ChatRenderer { node, parser.highlightRegex, parser.highlightWords, - (text) => createHighlightNode(text, parser.highlightColor) + (text) => createHighlightNode(text, parser.highlightColor), ); if (highlighted && parser.highlightWholeMessage) { node.className += ' ChatMessage--highlighted'; @@ -537,7 +550,7 @@ class ChatRenderer { !Byond.IS_LTE_IE8 && MESSAGE_TYPES.find( (typeDef) => - typeDef.selector && node.querySelector(typeDef.selector) + typeDef.selector && node.querySelector(typeDef.selector), ); message.type = typeDef?.type || MESSAGE_TYPE_UNKNOWN; } @@ -598,7 +611,7 @@ class ChatRenderer { // Remove pruned messages from the message array this.messages = this.messages.filter( - (message) => message.node !== 'pruned' + (message) => message.node !== 'pruned', ); logger.log(`pruned ${fromIndex} visible messages`); } @@ -607,7 +620,7 @@ class ChatRenderer { { const fromIndex = Math.max( 0, - this.messages.length - this.persistentMessageLimit + this.messages.length - this.persistentMessageLimit, ); if (fromIndex > 0) { this.messages = this.messages.slice(fromIndex); diff --git a/tgui/packages/tgui-panel/chat/replaceInTextNode.js b/tgui/packages/tgui-panel/chat/replaceInTextNode.js index 5c345e90a1..4c91b604da 100644 --- a/tgui/packages/tgui-panel/chat/replaceInTextNode.js +++ b/tgui/packages/tgui-panel/chat/replaceInTextNode.js @@ -149,7 +149,7 @@ export const highlightNode = ( node, regex, words, - createNode = createHighlightNode + createNode = createHighlightNode, ) => { if (!createNode) { createNode = createHighlightNode; diff --git a/tgui/packages/tgui-panel/game/hooks.ts b/tgui/packages/tgui-panel/game/hooks.ts index 859aaa09a4..40a74ff44a 100644 --- a/tgui/packages/tgui-panel/game/hooks.ts +++ b/tgui/packages/tgui-panel/game/hooks.ts @@ -4,9 +4,10 @@ * @license MIT */ -import { useSelector } from 'common/redux'; +import { useSelector } from 'tgui/backend'; + import { selectGame } from './selectors'; -export const useGame = (context) => { - return useSelector(context, selectGame); +export const useGame = () => { + return useSelector(selectGame); }; diff --git a/tgui/packages/tgui-panel/game/middleware.js b/tgui/packages/tgui-panel/game/middleware.js index 53dd45bb46..1cf80a7ac6 100644 --- a/tgui/packages/tgui-panel/game/middleware.js +++ b/tgui/packages/tgui-panel/game/middleware.js @@ -6,8 +6,8 @@ import { pingSoft, pingSuccess } from '../ping/actions'; import { connectionLost, connectionRestored, roundRestarted } from './actions'; -import { selectGame } from './selectors'; import { CONNECTION_LOST_AFTER } from './constants'; +import { selectGame } from './selectors'; const withTimestamp = (action) => ({ ...action, diff --git a/tgui/packages/tgui-panel/index.tsx b/tgui/packages/tgui-panel/index.tsx index 2c5ef175a8..c1fb8ac792 100644 --- a/tgui/packages/tgui-panel/index.tsx +++ b/tgui/packages/tgui-panel/index.tsx @@ -11,11 +11,13 @@ import './styles/themes/vchatdark.scss'; import { perf } from 'common/perf'; import { combineReducers } from 'common/redux'; -import { setupHotReloading } from 'tgui-dev-server/link/client.cjs'; +import { setGlobalStore } from 'tgui/backend'; import { setupGlobalEvents } from 'tgui/events'; import { captureExternalLinks } from 'tgui/links'; import { createRenderer } from 'tgui/renderer'; -import { configureStore, StoreProvider } from 'tgui/store'; +import { configureStore } from 'tgui/store'; +import { setupHotReloading } from 'tgui-dev-server/link/client.cjs'; + import { audioMiddleware, audioReducer } from './audio'; import { chatMiddleware, chatReducer } from './chat'; import { gameMiddleware, gameReducer } from './game'; @@ -23,7 +25,6 @@ import { setupPanelFocusHacks } from './panelFocus'; import { pingMiddleware, pingReducer } from './ping'; import { settingsMiddleware, settingsReducer } from './settings'; import { telemetryMiddleware } from './telemetry'; -import { setGlobalStore } from 'tgui/backend'; perf.mark('inception', window.performance?.timing?.navigationStart); perf.mark('init'); @@ -52,11 +53,7 @@ const renderApp = createRenderer(() => { setGlobalStore(store); const { Panel } = require('./Panel'); - return ( - - - - ); + return ; }); const setupApp = () => { @@ -85,14 +82,14 @@ const setupApp = () => { Byond.winset('browseroutput', { 'is-visible': true, 'is-disabled': false, - 'pos': '0x0', - 'size': '0x0', + pos: '0x0', + size: '0x0', }); // Resize the panel to match the non-browser output Byond.winget('output').then((output: { size: string }) => { Byond.winset('browseroutput', { - 'size': output.size, + size: output.size, }); }); @@ -113,7 +110,7 @@ const setupApp = () => { ], () => { renderApp(); - } + }, ); } }; diff --git a/tgui/packages/tgui-panel/package.json b/tgui/packages/tgui-panel/package.json index 7aab57e169..f020764a31 100644 --- a/tgui/packages/tgui-panel/package.json +++ b/tgui/packages/tgui-panel/package.json @@ -6,8 +6,9 @@ "@types/node": "^14.x", "@types/react": "^18.2.42", "common": "workspace:*", - "dompurify": "^2.3.1", - "inferno": "^7.4.8", + "dompurify": "^2.4.4", + "react": "^18.2.0", + "react-dom": "^18.2.0", "tgui": "workspace:*", "tgui-dev-server": "workspace:*", "tgui-polyfill": "workspace:*" diff --git a/tgui/packages/tgui-panel/ping/PingIndicator.jsx b/tgui/packages/tgui-panel/ping/PingIndicator.jsx index aadbd1c134..b2355820e5 100644 --- a/tgui/packages/tgui-panel/ping/PingIndicator.jsx +++ b/tgui/packages/tgui-panel/ping/PingIndicator.jsx @@ -6,12 +6,12 @@ import { Color } from 'common/color'; import { toFixed } from 'common/math'; -import { useSelector } from 'common/redux'; +import { useSelector } from 'tgui/backend'; import { Box } from 'tgui/components'; import { selectPing } from './selectors'; -export const PingIndicator = (props, context) => { - const ping = useSelector(context, selectPing); +export const PingIndicator = (props) => { + const ping = useSelector(selectPing); const color = Color.lookup(ping.networkQuality, [ new Color(220, 40, 40), new Color(220, 200, 40), diff --git a/tgui/packages/tgui-panel/ping/reducer.ts b/tgui/packages/tgui-panel/ping/reducer.ts index fcab775548..10531d23d8 100644 --- a/tgui/packages/tgui-panel/ping/reducer.ts +++ b/tgui/packages/tgui-panel/ping/reducer.ts @@ -5,8 +5,13 @@ */ import { clamp01, scale } from 'common/math'; + import { pingFail, pingSuccess } from './actions'; -import { PING_MAX_FAILS, PING_ROUNDTRIP_BEST, PING_ROUNDTRIP_WORST } from './constants'; +import { + PING_MAX_FAILS, + PING_ROUNDTRIP_BEST, + PING_ROUNDTRIP_WORST, +} from './constants'; type PingState = { roundtrip: number | undefined; @@ -35,7 +40,7 @@ export const pingReducer = (state = {} as PingState, action) => { if (type === pingFail.type) { const { failCount = 0 } = state; const networkQuality = clamp01( - state.networkQuality - failCount / PING_MAX_FAILS + state.networkQuality - failCount / PING_MAX_FAILS, ); const nextState: PingState = { ...state, diff --git a/tgui/packages/tgui-panel/reconnect.tsx b/tgui/packages/tgui-panel/reconnect.tsx index 589bc7f110..69bab020bd 100644 --- a/tgui/packages/tgui-panel/reconnect.tsx +++ b/tgui/packages/tgui-panel/reconnect.tsx @@ -1,5 +1,6 @@ +import { useDispatch } from 'tgui/backend'; import { Button } from 'tgui/components'; -import { useDispatch } from 'common/redux'; + import { dismissWarning } from './game/actions'; let url: string | null = null; @@ -13,18 +14,19 @@ setInterval(() => { }); }, 5000); -export const ReconnectButton = (props, context) => { +export const ReconnectButton = (props) => { if (!url) { return null; } - const dispatch = useDispatch(context); + const dispatch = useDispatch(); return ( <> diff --git a/tgui/packages/tgui-panel/settings/SettingsPanel.jsx b/tgui/packages/tgui-panel/settings/SettingsPanel.jsx index 7dd1478ed1..a2f171259d 100644 --- a/tgui/packages/tgui-panel/settings/SettingsPanel.jsx +++ b/tgui/packages/tgui-panel/settings/SettingsPanel.jsx @@ -6,18 +6,47 @@ import { toFixed } from 'common/math'; import { useLocalState } from 'tgui/backend'; -import { useDispatch, useSelector } from 'common/redux'; -import { Box, Button, ColorBox, Divider, Dropdown, Flex, Input, LabeledList, NumberInput, Section, Stack, Tabs, TextArea } from 'tgui/components'; +import { useDispatch, useSelector } from 'tgui/backend'; +import { + Box, + Button, + ColorBox, + Divider, + Dropdown, + Flex, + Input, + LabeledList, + NumberInput, + Section, + Stack, + Tabs, + TextArea, +} from 'tgui/components'; import { ChatPageSettings } from '../chat'; -import { rebuildChat, saveChatToDisk, purgeChatMessageArchive } from '../chat/actions'; +import { + rebuildChat, + saveChatToDisk, + purgeChatMessageArchive, +} from '../chat/actions'; import { THEMES } from '../themes'; -import { changeSettingsTab, updateSettings, addHighlightSetting, removeHighlightSetting, updateHighlightSetting } from './actions'; +import { + changeSettingsTab, + updateSettings, + addHighlightSetting, + removeHighlightSetting, + updateHighlightSetting, +} from './actions'; import { SETTINGS_TABS, FONTS, MAX_HIGHLIGHT_SETTINGS } from './constants'; -import { selectActiveTab, selectSettings, selectHighlightSettings, selectHighlightSettingById } from './selectors'; +import { + selectActiveTab, + selectSettings, + selectHighlightSettings, + selectHighlightSettingById, +} from './selectors'; -export const SettingsPanel = (props, context) => { - const activeTab = useSelector(context, selectActiveTab); - const dispatch = useDispatch(context); +export const SettingsPanel = (props) => { + const activeTab = useSelector(selectActiveTab); + const dispatch = useDispatch(); return ( @@ -31,9 +60,10 @@ export const SettingsPanel = (props, context) => { dispatch( changeSettingsTab({ tabId: tab.id, - }) + }), ) - }> + } + > {tab.name} ))} @@ -51,11 +81,11 @@ export const SettingsPanel = (props, context) => { ); }; -export const SettingsGeneral = (props, context) => { +export const SettingsGeneral = (props) => { const { theme, fontFamily, fontSize, lineHeight, showReconnectWarning } = - useSelector(context, selectSettings); - const dispatch = useDispatch(context); - const [freeFont, setFreeFont] = useLocalState(context, 'freeFont', false); + useSelector(selectSettings); + const dispatch = useDispatch(); + const [freeFont, setFreeFont] = useLocalState('freeFont', false); return (
@@ -67,7 +97,7 @@ export const SettingsGeneral = (props, context) => { dispatch( updateSettings({ theme: value, - }) + }), ) } /> @@ -83,7 +113,7 @@ export const SettingsGeneral = (props, context) => { dispatch( updateSettings({ fontFamily: value, - }) + }), ) } /> @@ -94,7 +124,7 @@ export const SettingsGeneral = (props, context) => { dispatch( updateSettings({ fontFamily: value, - }) + }), ) } /> @@ -127,7 +157,7 @@ export const SettingsGeneral = (props, context) => { dispatch( updateSettings({ fontSize: value, - }) + }), ) } /> @@ -145,7 +175,7 @@ export const SettingsGeneral = (props, context) => { dispatch( updateSettings({ lineHeight: value, - }) + }), ) } /> @@ -160,7 +190,7 @@ export const SettingsGeneral = (props, context) => { dispatch( updateSettings({ showReconnectWarning: !showReconnectWarning, - }) + }), ) } /> @@ -170,14 +200,14 @@ export const SettingsGeneral = (props, context) => { ); }; -export const MessageLimits = (props, context) => { - const dispatch = useDispatch(context); +export const MessageLimits = (props) => { + const dispatch = useDispatch(); const { visibleMessageLimit, persistentMessageLimit, combineMessageLimit, combineIntervalLimit, - } = useSelector(context, selectSettings); + } = useSelector(selectSettings); return (
@@ -194,7 +224,7 @@ export const MessageLimits = (props, context) => { dispatch( updateSettings({ visibleMessageLimit: value, - }) + }), ) } /> @@ -212,7 +242,7 @@ export const MessageLimits = (props, context) => { dispatch( updateSettings({ persistentMessageLimit: value, - }) + }), ) } /> @@ -230,7 +260,7 @@ export const MessageLimits = (props, context) => { dispatch( updateSettings({ combineMessageLimit: value, - }) + }), ) } /> @@ -249,7 +279,7 @@ export const MessageLimits = (props, context) => { dispatch( updateSettings({ combineIntervalLimit: value, - }) + }), ) } /> @@ -259,17 +289,11 @@ export const MessageLimits = (props, context) => { ); }; -export const ExportTab = (props, context) => { - const dispatch = useDispatch(context); - const { logRetainDays, logLineCount, totalStoredMessages } = useSelector( - context, - selectSettings - ); - const [purgeConfirm, setPurgeConfirm] = useLocalState( - context, - 'purgeConfirm', - 0 - ); +export const ExportTab = (props) => { + const dispatch = useDispatch(); + const { logRetainDays, logLineCount, totalStoredMessages } = + useSelector(selectSettings); + const [purgeConfirm, setPurgeConfirm] = useLocalState('purgeConfirm', 0); return (
@@ -302,7 +326,7 @@ export const ExportTab = (props, context) => { dispatch( updateSettings({ logLineCount: value, - }) + }), ) } /> @@ -322,7 +346,8 @@ export const ExportTab = (props, context) => { onClick={() => { dispatch(purgeChatMessageArchive()); setPurgeConfirm(2); - }}> + }} + > {purgeConfirm > 1 ? 'Purged!' : 'Are you sure?'} ) : ( @@ -334,7 +359,8 @@ export const ExportTab = (props, context) => { setTimeout(() => { setPurgeConfirm(false); }, 5000); - }}> + }} + > Purge message archive )} @@ -342,9 +368,9 @@ export const ExportTab = (props, context) => { ); }; -const TextHighlightSettings = (props, context) => { - const highlightSettings = useSelector(context, selectHighlightSettings); - const dispatch = useDispatch(context); +const TextHighlightSettings = (props) => { + const highlightSettings = useSelector(selectHighlightSettings); + const dispatch = useDispatch(); return (
@@ -383,10 +409,10 @@ const TextHighlightSettings = (props, context) => { ); }; -const TextHighlightSetting = (props, context) => { +const TextHighlightSetting = (props) => { const { id, ...rest } = props; - const highlightSettingById = useSelector(context, selectHighlightSettingById); - const dispatch = useDispatch(context); + const highlightSettingById = useSelector(selectHighlightSettingById); + const dispatch = useDispatch(); const { highlightColor, highlightText, @@ -408,7 +434,7 @@ const TextHighlightSetting = (props, context) => { dispatch( removeHighlightSetting({ id: id, - }) + }), ) } /> @@ -424,7 +450,7 @@ const TextHighlightSetting = (props, context) => { updateHighlightSetting({ id: id, highlightBlacklist: !highlightBlacklist, - }) + }), ) } /> @@ -440,7 +466,7 @@ const TextHighlightSetting = (props, context) => { updateHighlightSetting({ id: id, highlightWholeMessage: !highlightWholeMessage, - }) + }), ) } /> @@ -456,7 +482,7 @@ const TextHighlightSetting = (props, context) => { updateHighlightSetting({ id: id, matchWord: !matchWord, - }) + }), ) } /> @@ -471,7 +497,7 @@ const TextHighlightSetting = (props, context) => { updateHighlightSetting({ id: id, matchCase: !matchCase, - }) + }), ) } /> @@ -488,7 +514,7 @@ const TextHighlightSetting = (props, context) => { updateHighlightSetting({ id: id, highlightColor: value, - }) + }), ) } /> @@ -503,7 +529,7 @@ const TextHighlightSetting = (props, context) => { updateHighlightSetting({ id: id, highlightText: value, - }) + }), ) } /> @@ -517,7 +543,7 @@ const TextHighlightSetting = (props, context) => { updateHighlightSetting({ id: id, blacklistText: value, - }) + }), ) } /> diff --git a/tgui/packages/tgui-panel/settings/actions.ts b/tgui/packages/tgui-panel/settings/actions.ts index 1550bef80b..55e5db1126 100644 --- a/tgui/packages/tgui-panel/settings/actions.ts +++ b/tgui/packages/tgui-panel/settings/actions.ts @@ -5,6 +5,7 @@ */ import { createAction } from 'common/redux'; + import { createHighlightSetting } from './model'; export const updateSettings = createAction('settings/update'); @@ -16,11 +17,11 @@ export const addHighlightSetting = createAction( 'settings/addHighlightSetting', () => ({ payload: createHighlightSetting(), - }) + }), ); export const removeHighlightSetting = createAction( - 'settings/removeHighlightSetting' + 'settings/removeHighlightSetting', ); export const updateHighlightSetting = createAction( - 'settings/updateHighlightSetting' + 'settings/updateHighlightSetting', ); diff --git a/tgui/packages/tgui-panel/settings/hooks.ts b/tgui/packages/tgui-panel/settings/hooks.ts index da46322f9a..b37b49a22c 100644 --- a/tgui/packages/tgui-panel/settings/hooks.ts +++ b/tgui/packages/tgui-panel/settings/hooks.ts @@ -4,13 +4,14 @@ * @license MIT */ -import { useDispatch, useSelector } from 'common/redux'; -import { updateSettings, toggleSettings } from './actions'; +import { useDispatch, useSelector } from 'tgui/backend'; + +import { toggleSettings, updateSettings } from './actions'; import { selectSettings } from './selectors'; -export const useSettings = (context) => { - const settings = useSelector(context, selectSettings); - const dispatch = useDispatch(context); +export const useSettings = () => { + const settings = useSelector(selectSettings); + const dispatch = useDispatch(); return { ...settings, visible: settings.view.visible, diff --git a/tgui/packages/tgui-panel/settings/middleware.js b/tgui/packages/tgui-panel/settings/middleware.js index cef082213d..edb16a51c4 100644 --- a/tgui/packages/tgui-panel/settings/middleware.js +++ b/tgui/packages/tgui-panel/settings/middleware.js @@ -5,10 +5,17 @@ */ import { storage } from 'common/storage'; + import { setClientTheme } from '../themes'; -import { loadSettings, updateSettings, addHighlightSetting, removeHighlightSetting, updateHighlightSetting } from './actions'; -import { selectSettings } from './selectors'; +import { + addHighlightSetting, + loadSettings, + removeHighlightSetting, + updateHighlightSetting, + updateSettings, +} from './actions'; import { FONTS_DISABLED } from './constants'; +import { selectSettings } from './selectors'; let overrideRule = null; let overrideFontFamily = null; diff --git a/tgui/packages/tgui-panel/settings/reducer.js b/tgui/packages/tgui-panel/settings/reducer.js index af0ca15190..82e14ffa3e 100644 --- a/tgui/packages/tgui-panel/settings/reducer.js +++ b/tgui/packages/tgui-panel/settings/reducer.js @@ -4,9 +4,18 @@ * @license MIT */ -import { changeSettingsTab, loadSettings, openChatSettings, toggleSettings, updateSettings, addHighlightSetting, removeHighlightSetting, updateHighlightSetting } from './actions'; +import { + addHighlightSetting, + changeSettingsTab, + loadSettings, + openChatSettings, + removeHighlightSetting, + toggleSettings, + updateHighlightSetting, + updateSettings, +} from './actions'; +import { FONTS, MAX_HIGHLIGHT_SETTINGS, SETTINGS_TABS } from './constants'; import { createDefaultHighlightSetting } from './model'; -import { SETTINGS_TABS, FONTS, MAX_HIGHLIGHT_SETTINGS } from './constants'; const defaultHighlightSetting = createDefaultHighlightSetting(); @@ -139,7 +148,7 @@ export const settingsReducer = (state = initialState, action) => { } else { delete nextState.highlightSettingById[id]; nextState.highlightSettings = nextState.highlightSettings.filter( - (sid) => sid !== id + (sid) => sid !== id, ); if (!nextState.highlightSettings.length) { nextState.highlightSettings.push(defaultHighlightSetting.id); diff --git a/tgui/packages/tgui-panel/styles/main.scss b/tgui/packages/tgui-panel/styles/main.scss index ee96a9c5a6..08e60d18ee 100644 --- a/tgui/packages/tgui-panel/styles/main.scss +++ b/tgui/packages/tgui-panel/styles/main.scss @@ -10,7 +10,7 @@ @use '~tgui/styles/base.scss' with ( $color-bg: #202020, $color-bg-section: color.adjust(#202020, $lightness: -5%), - $color-bg-grad-spread: 0%, + $color-bg-grad-spread: 0% ); // Core styles diff --git a/tgui/packages/tgui-panel/styles/themes/light.scss b/tgui/packages/tgui-panel/styles/themes/light.scss index 1f752d51d6..19ddd3642c 100644 --- a/tgui/packages/tgui-panel/styles/themes/light.scss +++ b/tgui/packages/tgui-panel/styles/themes/light.scss @@ -22,7 +22,7 @@ $color-fg: #000000, $color-bg: #eeeeee, $color-bg-section: #ffffff, - $color-bg-grad-spread: 0%, + $color-bg-grad-spread: 0% ); // A fat warning to anyone who wants to use this: this only half works. diff --git a/tgui/packages/tgui-panel/styles/themes/vchatlight.scss b/tgui/packages/tgui-panel/styles/themes/vchatlight.scss index 9acaef59d8..1679f55976 100644 --- a/tgui/packages/tgui-panel/styles/themes/vchatlight.scss +++ b/tgui/packages/tgui-panel/styles/themes/vchatlight.scss @@ -7,23 +7,23 @@ @use 'sass:meta'; @use '~tgui/styles/colors.scss' with ( - $primary: #ffffff, - $bg-lightness: -25%, - $fg-lightness: -10%, - $label: #3b3b3b, - // Makes button look actually grey due to weird maths. - $grey: #ffffff, - // Commenting out color maps will adjust all colors based on the lightness - // settings above, but will add extra 10KB to the theme. - // $fg-map-keys: (), - // $bg-map-keys: (), - ); + $primary: #ffffff, + $bg-lightness: -25%, + $fg-lightness: -10%, + $label: #3b3b3b, + // Makes button look actually grey due to weird maths. + $grey: #ffffff, + // Commenting out color maps will adjust all colors based on the lightness + // settings above, but will add extra 10KB to the theme. + // $fg-map-keys: (), + // $bg-map-keys: (), +); @use '~tgui/styles/base.scss' with ( - $color-fg: #000000, - $color-bg: #eeeeee, - $color-bg-section: #ffffff, - $color-bg-grad-spread: 0%, - ); + $color-fg: #000000, + $color-bg: #eeeeee, + $color-bg-section: #ffffff, + $color-bg-grad-spread: 0% +); // A fat warning to anyone who wants to use this: this only half works. // It was made almost purely for the nuke ui, and requires a good amount of manual hacks to get it working as intended. diff --git a/tgui/packages/tgui-panel/telemetry.js b/tgui/packages/tgui-panel/telemetry.js index bade3c6391..3b1eb9a950 100644 --- a/tgui/packages/tgui-panel/telemetry.js +++ b/tgui/packages/tgui-panel/telemetry.js @@ -58,7 +58,7 @@ export const telemetryMiddleware = (store) => { let telemetryMutated = false; const duplicateConnection = telemetry.connections.find((conn) => - connectionsMatch(conn, client) + connectionsMatch(conn, client), ); if (!duplicateConnection) { telemetryMutated = true; diff --git a/tgui/packages/tgui-polyfill/00-html5shiv.js b/tgui/packages/tgui-polyfill/00-html5shiv.js deleted file mode 100644 index fcde92e6f7..0000000000 --- a/tgui/packages/tgui-polyfill/00-html5shiv.js +++ /dev/null @@ -1,336 +0,0 @@ -/** - * @file - * @copyright 2014 Alexander Farkas - * @license MIT - */ - -/* eslint-disable */ -(function (window, document) { - /*jshint evil:true */ - /** version */ - var version = '3.7.3'; - - /** Preset options */ - var options = window.html5 || {}; - - /** Used to skip problem elements */ - var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; - - /** Not all elements can be cloned in IE **/ - var saveClones = - /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; - - /** Detect whether the browser supports default html5 styles */ - var supportsHtml5Styles; - - /** Name of the expando, to work with multiple documents or to re-shiv one document */ - var expando = '_html5shiv'; - - /** The id for the the documents expando */ - var expanID = 0; - - /** Cached data for each document */ - var expandoData = {}; - - /** Detect whether the browser supports unknown elements */ - var supportsUnknownElements; - - (function () { - try { - var a = document.createElement('a'); - a.innerHTML = ''; - //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles - supportsHtml5Styles = 'hidden' in a; - - supportsUnknownElements = - a.childNodes.length == 1 || - (function () { - // assign a false positive if unable to shiv - document.createElement('a'); - var frag = document.createDocumentFragment(); - return ( - typeof frag.cloneNode == 'undefined' || - typeof frag.createDocumentFragment == 'undefined' || - typeof frag.createElement == 'undefined' - ); - })(); - } catch (e) { - // assign a false positive if detection fails => unable to shiv - supportsHtml5Styles = true; - supportsUnknownElements = true; - } - })(); - - /*--------------------------------------------------------------------------*/ - - /** - * Creates a style sheet with the given CSS text and adds it to the document. - * @private - * @param {Document} ownerDocument The document. - * @param {String} cssText The CSS text. - * @returns {StyleSheet} The style element. - */ - function addStyleSheet(ownerDocument, cssText) { - var p = ownerDocument.createElement('p'), - parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; - - p.innerHTML = 'x'; - return parent.insertBefore(p.lastChild, parent.firstChild); - } - - /** - * Returns the value of `html5.elements` as an array. - * @private - * @returns {Array} An array of shived element node names. - */ - function getElements() { - var elements = html5.elements; - return typeof elements == 'string' ? elements.split(' ') : elements; - } - - /** - * Extends the built-in list of html5 elements - * @memberOf html5 - * @param {String|Array} newElements whitespace separated list or array of new element names to shiv - * @param {Document} ownerDocument The context document. - */ - function addElements(newElements, ownerDocument) { - var elements = html5.elements; - if (typeof elements != 'string') { - elements = elements.join(' '); - } - if (typeof newElements != 'string') { - newElements = newElements.join(' '); - } - html5.elements = elements + ' ' + newElements; - shivDocument(ownerDocument); - } - - /** - * Returns the data associated to the given document - * @private - * @param {Document} ownerDocument The document. - * @returns {Object} An object of data. - */ - function getExpandoData(ownerDocument) { - var data = expandoData[ownerDocument[expando]]; - if (!data) { - data = {}; - expanID++; - ownerDocument[expando] = expanID; - expandoData[expanID] = data; - } - return data; - } - - /** - * returns a shived element for the given nodeName and document - * @memberOf html5 - * @param {String} nodeName name of the element - * @param {Document|DocumentFragment} ownerDocument The context document. - * @returns {Object} The shived element. - */ - function createElement(nodeName, ownerDocument, data) { - if (!ownerDocument) { - ownerDocument = document; - } - if (supportsUnknownElements) { - return ownerDocument.createElement(nodeName); - } - if (!data) { - data = getExpandoData(ownerDocument); - } - var node; - - if (data.cache[nodeName]) { - node = data.cache[nodeName].cloneNode(); - } else if (saveClones.test(nodeName)) { - node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); - } else { - node = data.createElem(nodeName); - } - - // Avoid adding some elements to fragments in IE < 9 because - // * Attributes like `name` or `type` cannot be set/changed once an element - // is inserted into a document/fragment - // * Link elements with `src` attributes that are inaccessible, as with - // a 403 response, will cause the tab/window to crash - // * Script elements appended to fragments will execute when their `src` - // or `text` property is set - return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; - } - - /** - * returns a shived DocumentFragment for the given document - * @memberOf html5 - * @param {Document} ownerDocument The context document. - * @returns {Object} The shived DocumentFragment. - */ - function createDocumentFragment(ownerDocument, data) { - if (!ownerDocument) { - ownerDocument = document; - } - if (supportsUnknownElements) { - return ownerDocument.createDocumentFragment(); - } - data = data || getExpandoData(ownerDocument); - var clone = data.frag.cloneNode(), - i = 0, - elems = getElements(), - l = elems.length; - for (; i < l; i++) { - clone.createElement(elems[i]); - } - return clone; - } - - /** - * Shivs the `createElement` and `createDocumentFragment` methods of the document. - * @private - * @param {Document|DocumentFragment} ownerDocument The document. - * @param {Object} data of the document. - */ - function shivMethods(ownerDocument, data) { - if (!data.cache) { - data.cache = {}; - data.createElem = ownerDocument.createElement; - data.createFrag = ownerDocument.createDocumentFragment; - data.frag = data.createFrag(); - } - - ownerDocument.createElement = function (nodeName) { - //abort shiv - if (!html5.shivMethods) { - return data.createElem(nodeName); - } - return createElement(nodeName, ownerDocument, data); - }; - - ownerDocument.createDocumentFragment = Function( - 'h,f', - 'return function(){' + - 'var n=f.cloneNode(),c=n.createElement;' + - 'h.shivMethods&&(' + - // unroll the `createElement` calls - getElements() - .join() - .replace(/[\w\-:]+/g, function (nodeName) { - data.createElem(nodeName); - data.frag.createElement(nodeName); - return 'c("' + nodeName + '")'; - }) + - ');return n}' - )(html5, data.frag); - } - - /*--------------------------------------------------------------------------*/ - - /** - * Shivs the given document. - * @memberOf html5 - * @param {Document} ownerDocument The document to shiv. - * @returns {Document} The shived document. - */ - function shivDocument(ownerDocument) { - if (!ownerDocument) { - ownerDocument = document; - } - var data = getExpandoData(ownerDocument); - - if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) { - data.hasCSS = !!addStyleSheet( - ownerDocument, - // corrects block display not defined in IE6/7/8/9 - 'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}' + - // adds styling not present in IE6/7/8/9 - 'mark{background:#FF0;color:#000}' + - // hides non-rendered elements - 'template{display:none}' - ); - } - if (!supportsUnknownElements) { - shivMethods(ownerDocument, data); - } - return ownerDocument; - } - - /*--------------------------------------------------------------------------*/ - - /** - * The `html5` object is exposed so that more elements can be shived and - * existing shiving can be detected on iframes. - * @type Object - * @example - * - * // options can be changed before the script is included - * html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false }; - */ - var html5 = { - /** - * An array or space separated string of node names of the elements to shiv. - * @memberOf html5 - * @type Array|String - */ - 'elements': - options.elements || - 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video', - - /** - * current version of html5shiv - */ - 'version': version, - - /** - * A flag to indicate that the HTML5 style sheet should be inserted. - * @memberOf html5 - * @type Boolean - */ - 'shivCSS': options.shivCSS !== false, - - /** - * Is equal to true if a browser supports creating unknown/HTML5 elements - * @memberOf html5 - * @type boolean - */ - 'supportsUnknownElements': supportsUnknownElements, - - /** - * A flag to indicate that the document's `createElement` and `createDocumentFragment` - * methods should be overwritten. - * @memberOf html5 - * @type Boolean - */ - 'shivMethods': options.shivMethods !== false, - - /** - * A string to describe the type of `html5` object ("default" or "default print"). - * @memberOf html5 - * @type String - */ - 'type': 'default', - - // shivs the document according to the specified `html5` object options - 'shivDocument': shivDocument, - - //creates a shived element - createElement: createElement, - - //creates a shived documentFragment - createDocumentFragment: createDocumentFragment, - - //extends list of elements - addElements: addElements, - }; - - /*--------------------------------------------------------------------------*/ - - // expose html5 - window.html5 = html5; - - // shiv the document - shivDocument(document); - - if (typeof module == 'object' && module.exports) { - module.exports = html5; - } -})(window, document); diff --git a/tgui/packages/tgui-polyfill/01-ie8.js b/tgui/packages/tgui-polyfill/01-ie8.js deleted file mode 100644 index 379b335562..0000000000 --- a/tgui/packages/tgui-polyfill/01-ie8.js +++ /dev/null @@ -1,773 +0,0 @@ -/** - * @file - * @copyright 2013 Andrea Giammarchi, WebReflection - * @license MIT - */ - -/* eslint-disable */ -(function (window) { - /*! (C) WebReflection Mit Style License */ - if (document.createEvent) return; - var DUNNOABOUTDOMLOADED = true, - READYEVENTDISPATCHED = false, - ONREADYSTATECHANGE = 'onreadystatechange', - DOMCONTENTLOADED = 'DOMContentLoaded', - SECRET = '__IE8__' + Math.random(), - // Object = window.Object, - defineProperty = - Object.defineProperty || - // just in case ... - function (object, property, descriptor) { - object[property] = descriptor.value; - }, - defineProperties = - Object.defineProperties || - // IE8 implemented defineProperty but not the plural... - function (object, descriptors) { - for (var key in descriptors) { - if (hasOwnProperty.call(descriptors, key)) { - try { - defineProperty(object, key, descriptors[key]); - } catch (o_O) { - if (window.console) { - console.log(key + ' failed on object:', object, o_O.message); - } - } - } - } - }, - getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor, - hasOwnProperty = Object.prototype.hasOwnProperty, - // here IE7 will break like a charm - ElementPrototype = window.Element.prototype, - TextPrototype = window.Text.prototype, - // none of above native constructors exist/are exposed - possiblyNativeEvent = /^[a-z]+$/, - // ^ actually could probably be just /^[a-z]+$/ - readyStateOK = /loaded|complete/, - types = {}, - div = document.createElement('div'), - html = document.documentElement, - removeAttribute = html.removeAttribute, - setAttribute = html.setAttribute, - valueDesc = function (value) { - return { - enumerable: true, - writable: true, - configurable: true, - value: value, - }; - }; - function commonEventLoop(currentTarget, e, $handlers, synthetic) { - for ( - var handler, - continuePropagation, - handlers = $handlers.slice(), - evt = enrich(e, currentTarget), - i = 0, - length = handlers.length; - i < length; - i++ - ) { - handler = handlers[i]; - if (typeof handler === 'object') { - if (typeof handler.handleEvent === 'function') { - handler.handleEvent(evt); - } - } else { - handler.call(currentTarget, evt); - } - if (evt.stoppedImmediatePropagation) break; - } - continuePropagation = !evt.stoppedPropagation; - /* - if (continuePropagation && !synthetic && !live(currentTarget)) { - evt.cancelBubble = true; - } - */ - return synthetic && continuePropagation && currentTarget.parentNode - ? currentTarget.parentNode.dispatchEvent(evt) - : !evt.defaultPrevented; - } - - function commonDescriptor(get, set) { - return { - // if you try with enumerable: true - // IE8 will miserably fail - configurable: true, - get: get, - set: set, - }; - } - - function commonTextContent(protoDest, protoSource, property) { - var descriptor = getOwnPropertyDescriptor(protoSource || protoDest, property); - defineProperty( - protoDest, - 'textContent', - commonDescriptor( - function () { - return descriptor.get.call(this); - }, - function (textContent) { - descriptor.set.call(this, textContent); - } - ) - ); - } - - function enrich(e, currentTarget) { - e.currentTarget = currentTarget; - e.eventPhase = - // AT_TARGET : BUBBLING_PHASE - e.target === e.currentTarget ? 2 : 3; - return e; - } - - function find(array, value) { - var i = array.length; - while (i-- && array[i] !== value); - return i; - } - - function getTextContent() { - if (this.tagName === 'BR') return '\n'; - var textNode = this.firstChild, - arrayContent = []; - while (textNode) { - if (textNode.nodeType !== 8 && textNode.nodeType !== 7) { - arrayContent.push(textNode.textContent); - } - textNode = textNode.nextSibling; - } - return arrayContent.join(''); - } - - function live(self) { - return self.nodeType !== 9 && html.contains(self); - } - - function onkeyup(e) { - var evt = document.createEvent('Event'); - evt.initEvent('input', true, true); - (e.srcElement || e.fromElement || document).dispatchEvent(evt); - } - - function onReadyState(e) { - if (!READYEVENTDISPATCHED && readyStateOK.test(document.readyState)) { - READYEVENTDISPATCHED = !READYEVENTDISPATCHED; - document.detachEvent(ONREADYSTATECHANGE, onReadyState); - e = document.createEvent('Event'); - e.initEvent(DOMCONTENTLOADED, true, true); - document.dispatchEvent(e); - } - } - - function getter(attr) { - return function () { - return html[attr] || (document.body && document.body[attr]) || 0; - }; - } - - function setTextContent(textContent) { - var node; - while ((node = this.lastChild)) { - this.removeChild(node); - } - /*jshint eqnull:true */ - if (textContent != null) { - this.appendChild(document.createTextNode(textContent)); - } - } - - function verify(self, e) { - if (!e) { - e = window.event; - } - if (!e.target) { - e.target = e.srcElement || e.fromElement || document; - } - if (!e.timeStamp) { - e.timeStamp = new Date().getTime(); - } - return e; - } - - // normalized textContent for: - // comment, script, style, text, title - commonTextContent(window.HTMLCommentElement.prototype, ElementPrototype, 'nodeValue'); - - commonTextContent(window.HTMLScriptElement.prototype, null, 'text'); - - commonTextContent(TextPrototype, null, 'nodeValue'); - - commonTextContent(window.HTMLTitleElement.prototype, null, 'text'); - - defineProperty( - window.HTMLStyleElement.prototype, - 'textContent', - (function (descriptor) { - return commonDescriptor( - function () { - return descriptor.get.call(this.styleSheet); - }, - function (textContent) { - descriptor.set.call(this.styleSheet, textContent); - } - ); - })(getOwnPropertyDescriptor(window.CSSStyleSheet.prototype, 'cssText')) - ); - - var opacityre = /\b\s*alpha\s*\(\s*opacity\s*=\s*(\d+)\s*\)/; - defineProperty(window.CSSStyleDeclaration.prototype, 'opacity', { - get: function () { - var m = this.filter.match(opacityre); - return m ? (m[1] / 100).toString() : ''; - }, - set: function (value) { - this.zoom = 1; - var found = false; - if (value < 1) { - value = ' alpha(opacity=' + Math.round(value * 100) + ')'; - } else { - value = ''; - } - this.filter = this.filter.replace(opacityre, function () { - found = true; - return value; - }); - if (!found && value) { - this.filter += value; - } - }, - }); - - defineProperties(ElementPrototype, { - // bonus - textContent: { - get: getTextContent, - set: setTextContent, - }, - // http://www.w3.org/TR/ElementTraversal/#interface-elementTraversal - firstElementChild: { - get: function () { - for (var childNodes = this.childNodes || [], i = 0, length = childNodes.length; i < length; i++) { - if (childNodes[i].nodeType == 1) return childNodes[i]; - } - }, - }, - lastElementChild: { - get: function () { - for (var childNodes = this.childNodes || [], i = childNodes.length; i--; ) { - if (childNodes[i].nodeType == 1) return childNodes[i]; - } - }, - }, - oninput: { - get: function () { - return this._oninput || null; - }, - set: function (oninput) { - if (this._oninput) { - this.removeEventListener('input', this._oninput); - this._oninput = oninput; - if (oninput) { - this.addEventListener('input', oninput); - } - } - }, - }, - previousElementSibling: { - get: function () { - var previousElementSibling = this.previousSibling; - while (previousElementSibling && previousElementSibling.nodeType != 1) { - previousElementSibling = previousElementSibling.previousSibling; - } - return previousElementSibling; - }, - }, - nextElementSibling: { - get: function () { - var nextElementSibling = this.nextSibling; - while (nextElementSibling && nextElementSibling.nodeType != 1) { - nextElementSibling = nextElementSibling.nextSibling; - } - return nextElementSibling; - }, - }, - childElementCount: { - get: function () { - for ( - var count = 0, childNodes = this.childNodes || [], i = childNodes.length; - i--; - count += childNodes[i].nodeType == 1 - ); - return count; - }, - }, - /* - // children would be an override - // IE8 already supports them but with comments too - // not just nodeType 1 - children: { - get: function () { - for(var - children = [], - childNodes = this.childNodes || [], - i = 0, length = childNodes.length; - i < length; i++ - ) { - if (childNodes[i].nodeType == 1) { - children.push(childNodes[i]); - } - } - return children; - } - }, - */ - // DOM Level 2 EventTarget methods and events - addEventListener: valueDesc(function (type, handler, capture) { - if (typeof handler !== 'function' && typeof handler !== 'object') return; - var self = this, - ontype = 'on' + type, - temple = self[SECRET] || defineProperty(self, SECRET, { value: {} })[SECRET], - currentType = temple[ontype] || (temple[ontype] = {}), - handlers = currentType.h || (currentType.h = []), - e, - attr; - if (!hasOwnProperty.call(currentType, 'w')) { - currentType.w = function (e) { - // e[SECRET] is a silent notification needed to avoid - // fired events during live test - return e[SECRET] || commonEventLoop(self, verify(self, e), handlers, false); - }; - // if not detected yet - if (!hasOwnProperty.call(types, ontype)) { - // and potentially a native event - if (possiblyNativeEvent.test(type)) { - // do this heavy thing - try { - // TODO: should I consider tagName too so that - // INPUT[ontype] could be different ? - e = document.createEventObject(); - // do not clone ever a node - // specially a document one ... - // use the secret to ignore them all - e[SECRET] = true; - // document a part if a node has never been - // added to any other node, fireEvent might - // behave very weirdly (read: trigger unspecified errors) - if (self.nodeType != 9) { - /*jshint eqnull:true */ - if (self.parentNode == null) { - div.appendChild(self); - } - if ((attr = self.getAttribute(ontype))) { - removeAttribute.call(self, ontype); - } - } - self.fireEvent(ontype, e); - types[ontype] = true; - } catch (meh) { - types[ontype] = false; - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - } - if (attr != null) { - setAttribute.call(self, ontype, attr); - } - } else { - // no need to bother since - // 'x-event' ain't native for sure - types[ontype] = false; - } - } - if ((currentType.n = types[ontype])) { - self.attachEvent(ontype, currentType.w); - } - } - if (find(handlers, handler) < 0) { - handlers[capture ? 'unshift' : 'push'](handler); - } - if (type === 'input') { - self.attachEvent('onkeyup', onkeyup); - } - }), - dispatchEvent: valueDesc(function (e) { - var self = this, - ontype = 'on' + e.type, - temple = self[SECRET], - currentType = temple && temple[ontype], - valid = !!currentType, - parentNode; - if (!e.target) e.target = self; - return ( - valid - ? currentType.n /* && live(self) */ - ? self.fireEvent(ontype, e) - : commonEventLoop(self, e, currentType.h, true) - : (parentNode = self.parentNode) /* && live(self) */ - ? parentNode.dispatchEvent(e) - : true, - !e.defaultPrevented - ); - }), - removeEventListener: valueDesc(function (type, handler, capture) { - if (typeof handler !== 'function' && typeof handler !== 'object') return; - var self = this, - ontype = 'on' + type, - temple = self[SECRET], - currentType = temple && temple[ontype], - handlers = currentType && currentType.h, - i = handlers ? find(handlers, handler) : -1; - if (-1 < i) handlers.splice(i, 1); - }), - }); - - /* this is not needed in IE8 - defineProperties(window.HTMLSelectElement.prototype, { - value: { - get: function () { - return this.options[this.selectedIndex].value; - } - } - }); - //*/ - - // EventTarget methods for Text nodes too - defineProperties(TextPrototype, { - addEventListener: valueDesc(ElementPrototype.addEventListener), - dispatchEvent: valueDesc(ElementPrototype.dispatchEvent), - removeEventListener: valueDesc(ElementPrototype.removeEventListener), - }); - - defineProperties(window.XMLHttpRequest.prototype, { - addEventListener: valueDesc(function (type, handler, capture) { - var self = this, - ontype = 'on' + type, - temple = self[SECRET] || defineProperty(self, SECRET, { value: {} })[SECRET], - currentType = temple[ontype] || (temple[ontype] = {}), - handlers = currentType.h || (currentType.h = []); - if (find(handlers, handler) < 0) { - if (!self[ontype]) { - self[ontype] = function () { - var e = document.createEvent('Event'); - e.initEvent(type, true, true); - self.dispatchEvent(e); - }; - } - handlers[capture ? 'unshift' : 'push'](handler); - } - }), - dispatchEvent: valueDesc(function (e) { - var self = this, - ontype = 'on' + e.type, - temple = self[SECRET], - currentType = temple && temple[ontype], - valid = !!currentType; - return ( - valid && - (currentType.n /* && live(self) */ ? self.fireEvent(ontype, e) : commonEventLoop(self, e, currentType.h, true)) - ); - }), - removeEventListener: valueDesc(ElementPrototype.removeEventListener), - }); - - var buttonGetter = getOwnPropertyDescriptor(Event.prototype, 'button').get; - defineProperties(window.Event.prototype, { - bubbles: valueDesc(true), - cancelable: valueDesc(true), - preventDefault: valueDesc(function () { - if (this.cancelable) { - this.returnValue = false; - } - }), - stopPropagation: valueDesc(function () { - this.stoppedPropagation = true; - this.cancelBubble = true; - }), - stopImmediatePropagation: valueDesc(function () { - this.stoppedImmediatePropagation = true; - this.stopPropagation(); - }), - initEvent: valueDesc(function (type, bubbles, cancelable) { - this.type = type; - this.bubbles = !!bubbles; - this.cancelable = !!cancelable; - if (!this.bubbles) { - this.stopPropagation(); - } - }), - pageX: { - get: function () { - return this._pageX || (this._pageX = this.clientX + window.scrollX - (html.clientLeft || 0)); - }, - }, - pageY: { - get: function () { - return this._pageY || (this._pageY = this.clientY + window.scrollY - (html.clientTop || 0)); - }, - }, - which: { - get: function () { - return this.keyCode ? this.keyCode : isNaN(this.button) ? undefined : this.button + 1; - }, - }, - charCode: { - get: function () { - return this.keyCode && this.type == 'keypress' ? this.keyCode : 0; - }, - }, - buttons: { - get: function () { - return buttonGetter.call(this); - }, - }, - button: { - get: function () { - var buttons = this.buttons; - return buttons & 1 ? 0 : buttons & 2 ? 2 : buttons & 4 ? 1 : undefined; - }, - }, - defaultPrevented: { - get: function () { - // if preventDefault() was never called, or returnValue not given a value - // then returnValue is undefined - var returnValue = this.returnValue, - undef; - return !(returnValue === undef || returnValue); - }, - }, - relatedTarget: { - get: function () { - var type = this.type; - if (type === 'mouseover') { - return this.fromElement; - } else if (type === 'mouseout') { - return this.toElement; - } else { - return null; - } - }, - }, - }); - - defineProperties(window.HTMLDocument.prototype, { - defaultView: { - get: function () { - return this.parentWindow; - }, - }, - textContent: { - get: function () { - return this.nodeType === 11 ? getTextContent.call(this) : null; - }, - set: function (textContent) { - if (this.nodeType === 11) { - setTextContent.call(this, textContent); - } - }, - }, - addEventListener: valueDesc(function (type, handler, capture) { - var self = this; - ElementPrototype.addEventListener.call(self, type, handler, capture); - // NOTE: it won't fire if already loaded, this is NOT a $.ready() shim! - // this behaves just like standard browsers - if (DUNNOABOUTDOMLOADED && type === DOMCONTENTLOADED && !readyStateOK.test(self.readyState)) { - DUNNOABOUTDOMLOADED = false; - self.attachEvent(ONREADYSTATECHANGE, onReadyState); - /* global top */ - if (window == top) { - (function gonna(e) { - try { - self.documentElement.doScroll('left'); - onReadyState(); - } catch (o_O) { - setTimeout(gonna, 50); - } - })(); - } - } - }), - dispatchEvent: valueDesc(ElementPrototype.dispatchEvent), - removeEventListener: valueDesc(ElementPrototype.removeEventListener), - createEvent: valueDesc(function (Class) { - var e; - if (Class !== 'Event') throw new Error('unsupported ' + Class); - e = document.createEventObject(); - e.timeStamp = new Date().getTime(); - return e; - }), - }); - - defineProperties(window.Window.prototype, { - getComputedStyle: valueDesc( - (function () { - var // partially grabbed from jQuery and Dean's hack - notpixel = /^(?:[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|))(?!px)[a-z%]+$/, - position = /^(top|right|bottom|left)$/, - re = /\-([a-z])/g, - place = function (match, $1) { - return $1.toUpperCase(); - }; - function ComputedStyle(_) { - this._ = _; - } - - ComputedStyle.prototype.getPropertyValue = function (name) { - var el = this._, - style = el.style, - currentStyle = el.currentStyle, - runtimeStyle = el.runtimeStyle, - result, - left, - rtLeft; - if (name == 'opacity') { - return style.opacity || '1'; - } - name = (name === 'float' ? 'style-float' : name).replace(re, place); - result = currentStyle ? currentStyle[name] : style[name]; - if (notpixel.test(result) && !position.test(name)) { - left = style.left; - rtLeft = runtimeStyle && runtimeStyle.left; - if (rtLeft) { - runtimeStyle.left = currentStyle.left; - } - style.left = name === 'fontSize' ? '1em' : result; - result = style.pixelLeft + 'px'; - style.left = left; - if (rtLeft) { - runtimeStyle.left = rtLeft; - } - } - /*jshint eqnull:true */ - return result == null ? result : result + '' || 'auto'; - }; - - // unsupported - function PseudoComputedStyle() {} - PseudoComputedStyle.prototype.getPropertyValue = function () { - return null; - }; - - return function (el, pseudo) { - return pseudo ? new PseudoComputedStyle(el) : new ComputedStyle(el); - }; - })() - ), - - addEventListener: valueDesc(function (type, handler, capture) { - var self = window, - ontype = 'on' + type, - handlers; - if (!self[ontype]) { - self[ontype] = function (e) { - return commonEventLoop(self, verify(self, e), handlers, false) && undefined; - }; - } - handlers = self[ontype][SECRET] || (self[ontype][SECRET] = []); - if (find(handlers, handler) < 0) { - handlers[capture ? 'unshift' : 'push'](handler); - } - }), - dispatchEvent: valueDesc(function (e) { - var method = window['on' + e.type]; - return method ? method.call(window, e) !== false && !e.defaultPrevented : true; - }), - removeEventListener: valueDesc(function (type, handler, capture) { - var ontype = 'on' + type, - handlers = (window[ontype] || Object)[SECRET], - i = handlers ? find(handlers, handler) : -1; - if (-1 < i) handlers.splice(i, 1); - }), - pageXOffset: { get: getter('scrollLeft') }, - pageYOffset: { get: getter('scrollTop') }, - scrollX: { get: getter('scrollLeft') }, - scrollY: { get: getter('scrollTop') }, - innerWidth: { get: getter('clientWidth') }, - innerHeight: { get: getter('clientHeight') }, - }); - - window.HTMLElement = window.Element; - - (function (styleSheets, HTML5Element, i) { - for (i = 0; i < HTML5Element.length; i++) document.createElement(HTML5Element[i]); - if (!styleSheets.length) document.createStyleSheet(''); - styleSheets[0].addRule(HTML5Element.join(','), 'display:block;'); - })(document.styleSheets, ['header', 'nav', 'section', 'article', 'aside', 'footer']); - - (function () { - if (document.createRange) return; - document.createRange = function createRange() { - return new Range(); - }; - - function getContents(start, end) { - var nodes = [start]; - while (start !== end) { - nodes.push((start = start.nextSibling)); - } - return nodes; - } - - function Range() {} - var proto = Range.prototype; - proto.cloneContents = function cloneContents() { - for ( - var fragment = this._start.ownerDocument.createDocumentFragment(), - nodes = getContents(this._start, this._end), - i = 0, - length = nodes.length; - i < length; - i++ - ) { - fragment.appendChild(nodes[i].cloneNode(true)); - } - return fragment; - }; - proto.cloneRange = function cloneRange() { - var range = new Range(); - range._start = this._start; - range._end = this._end; - return range; - }; - proto.deleteContents = function deleteContents() { - for ( - var parentNode = this._start.parentNode, - nodes = getContents(this._start, this._end), - i = 0, - length = nodes.length; - i < length; - i++ - ) { - parentNode.removeChild(nodes[i]); - } - }; - proto.extractContents = function extractContents() { - for ( - var fragment = this._start.ownerDocument.createDocumentFragment(), - nodes = getContents(this._start, this._end), - i = 0, - length = nodes.length; - i < length; - i++ - ) { - fragment.appendChild(nodes[i]); - } - return fragment; - }; - proto.setEndAfter = function setEndAfter(node) { - this._end = node; - }; - proto.setEndBefore = function setEndBefore(node) { - this._end = node.previousSibling; - }; - proto.setStartAfter = function setStartAfter(node) { - this._start = node.nextSibling; - }; - proto.setStartBefore = function setStartBefore(node) { - this._start = node; - }; - })(); -})(window); diff --git a/tgui/packages/tgui-polyfill/02-dom4.js b/tgui/packages/tgui-polyfill/02-dom4.js deleted file mode 100644 index d9ef036a3e..0000000000 --- a/tgui/packages/tgui-polyfill/02-dom4.js +++ /dev/null @@ -1,912 +0,0 @@ -/** - * @file - * @copyright 2013 Andrea Giammarchi, WebReflection - * @license MIT - */ - -/* eslint-disable */ -(function (window) { - 'use strict'; - /* jshint loopfunc: true, noempty: false*/ - // http://www.w3.org/TR/dom/#element - - function createDocumentFragment() { - return document.createDocumentFragment(); - } - - function createElement(nodeName) { - return document.createElement(nodeName); - } - - function enoughArguments(length, name) { - if (!length) throw new Error('Failed to construct ' + name + ': 1 argument required, but only 0 present.'); - } - - function mutationMacro(nodes) { - if (nodes.length === 1) { - return textNodeIfPrimitive(nodes[0]); - } - for (var fragment = createDocumentFragment(), list = slice.call(nodes), i = 0; i < nodes.length; i++) { - fragment.appendChild(textNodeIfPrimitive(list[i])); - } - return fragment; - } - - function textNodeIfPrimitive(node) { - return typeof node === 'object' ? node : document.createTextNode(node); - } - - for ( - var head, - property, - TemporaryPrototype, - TemporaryTokenList, - wrapVerifyToken, - document = window.document, - hOP = Object.prototype.hasOwnProperty, - defineProperty = - Object.defineProperty || - function (object, property, descriptor) { - if (hOP.call(descriptor, 'value')) { - object[property] = descriptor.value; - } else { - if (hOP.call(descriptor, 'get')) object.__defineGetter__(property, descriptor.get); - if (hOP.call(descriptor, 'set')) object.__defineSetter__(property, descriptor.set); - } - return object; - }, - indexOf = - [].indexOf || - function indexOf(value) { - var length = this.length; - while (length--) { - if (this[length] === value) { - break; - } - } - return length; - }, - // http://www.w3.org/TR/domcore/#domtokenlist - verifyToken = function (token) { - if (!token) { - throw 'SyntaxError'; - } else if (spaces.test(token)) { - throw 'InvalidCharacterError'; - } - return token; - }, - DOMTokenList = function (node) { - var noClassName = typeof node.className === 'undefined', - className = noClassName ? node.getAttribute('class') || '' : node.className, - isSVG = noClassName || typeof className === 'object', - value = (isSVG ? (noClassName ? className : className.baseVal) : className).replace(trim, ''); - if (value.length) { - properties.push.apply(this, value.split(spaces)); - } - this._isSVG = isSVG; - this._ = node; - }, - classListDescriptor = { - get: function get() { - return new DOMTokenList(this); - }, - set: function () {}, - }, - trim = /^\s+|\s+$/g, - spaces = /\s+/, - SPACE = '\x20', - CLASS_LIST = 'classList', - toggle = function toggle(token, force) { - if (this.contains(token)) { - if (!force) { - // force is not true (either false or omitted) - this.remove(token); - } - } else if (force === undefined || force) { - force = true; - this.add(token); - } - return !!force; - }, - DocumentFragmentPrototype = window.DocumentFragment && DocumentFragment.prototype, - Node = window.Node, - NodePrototype = (Node || Element).prototype, - CharacterData = window.CharacterData || Node, - CharacterDataPrototype = CharacterData && CharacterData.prototype, - DocumentType = window.DocumentType, - DocumentTypePrototype = DocumentType && DocumentType.prototype, - ElementPrototype = (window.Element || Node || window.HTMLElement).prototype, - HTMLSelectElement = window.HTMLSelectElement || createElement('select').constructor, - selectRemove = HTMLSelectElement.prototype.remove, - SVGElement = window.SVGElement, - properties = [ - 'matches', - ElementPrototype.matchesSelector || - ElementPrototype.webkitMatchesSelector || - ElementPrototype.khtmlMatchesSelector || - ElementPrototype.mozMatchesSelector || - ElementPrototype.msMatchesSelector || - ElementPrototype.oMatchesSelector || - function matches(selector) { - var parentNode = this.parentNode; - return !!parentNode && -1 < indexOf.call(parentNode.querySelectorAll(selector), this); - }, - 'closest', - function closest(selector) { - var parentNode = this, - matches; - while ( - // document has no .matches - (matches = parentNode && parentNode.matches) && - !parentNode.matches(selector) - ) { - parentNode = parentNode.parentNode; - } - return matches ? parentNode : null; - }, - 'prepend', - function prepend() { - var firstChild = this.firstChild, - node = mutationMacro(arguments); - if (firstChild) { - this.insertBefore(node, firstChild); - } else { - this.appendChild(node); - } - }, - 'append', - function append() { - this.appendChild(mutationMacro(arguments)); - }, - 'before', - function before() { - var parentNode = this.parentNode; - if (parentNode) { - parentNode.insertBefore(mutationMacro(arguments), this); - } - }, - 'after', - function after() { - var parentNode = this.parentNode, - nextSibling = this.nextSibling, - node = mutationMacro(arguments); - if (parentNode) { - if (nextSibling) { - parentNode.insertBefore(node, nextSibling); - } else { - parentNode.appendChild(node); - } - } - }, - // https://dom.spec.whatwg.org/#dom-element-toggleattribute - 'toggleAttribute', - function toggleAttribute(name, force) { - var had = this.hasAttribute(name); - if (1 < arguments.length) { - if (had && !force) this.removeAttribute(name); - else if (force && !had) this.setAttribute(name, ''); - } else if (had) this.removeAttribute(name); - else this.setAttribute(name, ''); - return this.hasAttribute(name); - }, - // WARNING - DEPRECATED - use .replaceWith() instead - 'replace', - function replace() { - this.replaceWith.apply(this, arguments); - }, - 'replaceWith', - function replaceWith() { - var parentNode = this.parentNode; - if (parentNode) { - parentNode.replaceChild(mutationMacro(arguments), this); - } - }, - 'remove', - function remove() { - var parentNode = this.parentNode; - if (parentNode) { - parentNode.removeChild(this); - } - }, - ], - slice = properties.slice, - i = properties.length; - i; - i -= 2 - ) { - property = properties[i - 2]; - if (!(property in ElementPrototype)) { - ElementPrototype[property] = properties[i - 1]; - } - // avoid unnecessary re-patch when the script is included - // gazillion times without any reason whatsoever - // https://github.com/WebReflection/dom4/pull/48 - if (property === 'remove' && !selectRemove._dom4) { - // see https://github.com/WebReflection/dom4/issues/19 - (HTMLSelectElement.prototype[property] = function () { - return 0 < arguments.length ? selectRemove.apply(this, arguments) : ElementPrototype.remove.call(this); - })._dom4 = true; - } - // see https://github.com/WebReflection/dom4/issues/18 - if (/^(?:before|after|replace|replaceWith|remove)$/.test(property)) { - if (CharacterData && !(property in CharacterDataPrototype)) { - CharacterDataPrototype[property] = properties[i - 1]; - } - if (DocumentType && !(property in DocumentTypePrototype)) { - DocumentTypePrototype[property] = properties[i - 1]; - } - } - // see https://github.com/WebReflection/dom4/pull/26 - if (/^(?:append|prepend)$/.test(property)) { - if (DocumentFragmentPrototype) { - if (!(property in DocumentFragmentPrototype)) { - DocumentFragmentPrototype[property] = properties[i - 1]; - } - } else { - try { - createDocumentFragment().constructor.prototype[property] = properties[i - 1]; - } catch (o_O) {} - } - } - } - - // most likely an IE9 only issue - // see https://github.com/WebReflection/dom4/issues/6 - if (!createElement('a').matches('a')) { - ElementPrototype[property] = (function (matches) { - return function (selector) { - return matches.call(this.parentNode ? this : createDocumentFragment().appendChild(this), selector); - }; - })(ElementPrototype[property]); - } - - // used to fix both old webkit and SVG - DOMTokenList.prototype = { - length: 0, - add: function add() { - for (var j = 0, token; j < arguments.length; j++) { - token = arguments[j]; - if (!this.contains(token)) { - properties.push.call(this, property); - } - } - if (this._isSVG) { - this._.setAttribute('class', '' + this); - } else { - this._.className = '' + this; - } - }, - contains: (function (indexOf) { - return function contains(token) { - i = indexOf.call(this, (property = verifyToken(token))); - return -1 < i; - }; - })( - [].indexOf || - function (token) { - i = this.length; - while (i-- && this[i] !== token) {} - return i; - } - ), - item: function item(i) { - return this[i] || null; - }, - remove: function remove() { - for (var j = 0, token; j < arguments.length; j++) { - token = arguments[j]; - if (this.contains(token)) { - properties.splice.call(this, i, 1); - } - } - if (this._isSVG) { - this._.setAttribute('class', '' + this); - } else { - this._.className = '' + this; - } - }, - toggle: toggle, - toString: function toString() { - return properties.join.call(this, SPACE); - }, - }; - - if (SVGElement && !(CLASS_LIST in SVGElement.prototype)) { - defineProperty(SVGElement.prototype, CLASS_LIST, classListDescriptor); - } - - // http://www.w3.org/TR/dom/#domtokenlist - // iOS 5.1 has completely screwed this property - // classList in ElementPrototype is false - // but it's actually there as getter - if (!(CLASS_LIST in document.documentElement)) { - defineProperty(ElementPrototype, CLASS_LIST, classListDescriptor); - } else { - // iOS 5.1 and Nokia ASHA do not support multiple add or remove - // trying to detect and fix that in here - TemporaryTokenList = createElement('div')[CLASS_LIST]; - TemporaryTokenList.add('a', 'b', 'a'); - if ('a\x20b' != TemporaryTokenList) { - // no other way to reach original methods in iOS 5.1 - TemporaryPrototype = TemporaryTokenList.constructor.prototype; - if (!('add' in TemporaryPrototype)) { - // ASHA double fails in here - TemporaryPrototype = window.TemporaryTokenList.prototype; - } - wrapVerifyToken = function (original) { - return function () { - var i = 0; - while (i < arguments.length) { - original.call(this, arguments[i++]); - } - }; - }; - TemporaryPrototype.add = wrapVerifyToken(TemporaryPrototype.add); - TemporaryPrototype.remove = wrapVerifyToken(TemporaryPrototype.remove); - // toggle is broken too ^_^ ... let's fix it - TemporaryPrototype.toggle = toggle; - } - } - - if (!('contains' in NodePrototype)) { - defineProperty(NodePrototype, 'contains', { - value: function (el) { - while (el && el !== this) el = el.parentNode; - return this === el; - }, - }); - } - - if (!('head' in document)) { - defineProperty(document, 'head', { - get: function () { - return head || (head = document.getElementsByTagName('head')[0]); - }, - }); - } - - // requestAnimationFrame partial polyfill - (function () { - for ( - var raf, - rAF = window.requestAnimationFrame, - cAF = window.cancelAnimationFrame, - prefixes = ['o', 'ms', 'moz', 'webkit'], - i = prefixes.length; - !cAF && i--; - - ) { - rAF = rAF || window[prefixes[i] + 'RequestAnimationFrame']; - cAF = window[prefixes[i] + 'CancelAnimationFrame'] || window[prefixes[i] + 'CancelRequestAnimationFrame']; - } - if (!cAF) { - // some FF apparently implemented rAF but no cAF - if (rAF) { - raf = rAF; - rAF = function (callback) { - var goOn = true; - raf(function () { - if (goOn) callback.apply(this, arguments); - }); - return function () { - goOn = false; - }; - }; - cAF = function (id) { - id(); - }; - } else { - rAF = function (callback) { - return setTimeout(callback, 15, 15); - }; - cAF = function (id) { - clearTimeout(id); - }; - } - } - window.requestAnimationFrame = rAF; - window.cancelAnimationFrame = cAF; - })(); - - // http://www.w3.org/TR/dom/#customevent - try { - new window.CustomEvent('?'); - } catch (o_O) { - window.CustomEvent = (function (eventName, defaultInitDict) { - // the infamous substitute - function CustomEvent(type, eventInitDict) { - /*jshint eqnull:true */ - var event = document.createEvent(eventName); - if (typeof type != 'string') { - throw new Error('An event name must be provided'); - } - if (eventName == 'Event') { - event.initCustomEvent = initCustomEvent; - } - if (eventInitDict == null) { - eventInitDict = defaultInitDict; - } - event.initCustomEvent(type, eventInitDict.bubbles, eventInitDict.cancelable, eventInitDict.detail); - return event; - } - - // attached at runtime - function initCustomEvent(type, bubbles, cancelable, detail) { - /*jshint validthis:true*/ - this.initEvent(type, bubbles, cancelable); - this.detail = detail; - } - - // that's it - return CustomEvent; - })( - // is this IE9 or IE10 ? - // where CustomEvent is there - // but not usable as construtor ? - window.CustomEvent - ? // use the CustomEvent interface in such case - 'CustomEvent' - : 'Event', - // otherwise the common compatible one - { - bubbles: false, - cancelable: false, - detail: null, - } - ); - } - - // window.Event as constructor - try { - new Event('_'); - } catch (o_O) { - /* jshint -W022 */ - o_O = (function ($Event) { - function Event(type, init) { - enoughArguments(arguments.length, 'Event'); - var out = document.createEvent('Event'); - if (!init) init = {}; - out.initEvent(type, !!init.bubbles, !!init.cancelable); - return out; - } - Event.prototype = $Event.prototype; - return Event; - })(window.Event || function Event() {}); - defineProperty(window, 'Event', { value: o_O }); - // Android 4 gotcha - if (Event !== o_O) Event = o_O; - } - - // window.KeyboardEvent as constructor - try { - new KeyboardEvent('_', {}); - } catch (o_O) { - /* jshint -W022 */ - o_O = (function ($KeyboardEvent) { - // code inspired by https://gist.github.com/termi/4654819 - var initType = 0, - defaults = { - char: '', - key: '', - location: 0, - ctrlKey: false, - shiftKey: false, - altKey: false, - metaKey: false, - altGraphKey: false, - repeat: false, - locale: navigator.language, - detail: 0, - bubbles: false, - cancelable: false, - keyCode: 0, - charCode: 0, - which: 0, - }, - eventType; - try { - var e = document.createEvent('KeyboardEvent'); - e.initKeyboardEvent('keyup', false, false, window, '+', 3, true, false, true, false, false); - initType = - ((e.keyIdentifier || e.key) == '+' && - (e.keyLocation || e.location) == 3 && - (e.ctrlKey ? (e.altKey ? 1 : 3) : e.shiftKey ? 2 : 4)) || - 9; - } catch (o_O) {} - eventType = 0 < initType ? 'KeyboardEvent' : 'Event'; - - function getModifier(init) { - for ( - var out = [], - keys = [ - 'ctrlKey', - 'Control', - 'shiftKey', - 'Shift', - 'altKey', - 'Alt', - 'metaKey', - 'Meta', - 'altGraphKey', - 'AltGraph', - ], - i = 0; - i < keys.length; - i += 2 - ) { - if (init[keys[i]]) out.push(keys[i + 1]); - } - return out.join(' '); - } - - function withDefaults(target, source) { - for (var key in source) { - if (source.hasOwnProperty(key) && !source.hasOwnProperty.call(target, key)) target[key] = source[key]; - } - return target; - } - - function withInitValues(key, out, init) { - try { - out[key] = init[key]; - } catch (o_O) {} - } - - function KeyboardEvent(type, init) { - enoughArguments(arguments.length, 'KeyboardEvent'); - init = withDefaults(init || {}, defaults); - var out = document.createEvent(eventType), - ctrlKey = init.ctrlKey, - shiftKey = init.shiftKey, - altKey = init.altKey, - metaKey = init.metaKey, - altGraphKey = init.altGraphKey, - modifiers = initType > 3 ? getModifier(init) : null, - key = String(init.key), - chr = String(init.char), - location = init.location, - keyCode = init.keyCode || ((init.keyCode = key) && key.charCodeAt(0)) || 0, - charCode = init.charCode || ((init.charCode = chr) && chr.charCodeAt(0)) || 0, - bubbles = init.bubbles, - cancelable = init.cancelable, - repeat = init.repeat, - locale = init.locale, - view = init.view || window, - args; - if (!init.which) init.which = init.keyCode; - if ('initKeyEvent' in out) { - out.initKeyEvent(type, bubbles, cancelable, view, ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode); - } else if (0 < initType && 'initKeyboardEvent' in out) { - args = [type, bubbles, cancelable, view]; - switch (initType) { - case 1: - args.push(key, location, ctrlKey, shiftKey, altKey, metaKey, altGraphKey); - break; - case 2: - args.push(ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode); - break; - case 3: - args.push(key, location, ctrlKey, altKey, shiftKey, metaKey, altGraphKey); - break; - case 4: - args.push(key, location, modifiers, repeat, locale); - break; - default: - args.push(char, key, location, modifiers, repeat, locale); - } - out.initKeyboardEvent.apply(out, args); - } else { - out.initEvent(type, bubbles, cancelable); - } - for (key in out) { - if (defaults.hasOwnProperty(key) && out[key] !== init[key]) { - withInitValues(key, out, init); - } - } - return out; - } - KeyboardEvent.prototype = $KeyboardEvent.prototype; - return KeyboardEvent; - })(window.KeyboardEvent || function KeyboardEvent() {}); - defineProperty(window, 'KeyboardEvent', { value: o_O }); - // Android 4 gotcha - if (KeyboardEvent !== o_O) KeyboardEvent = o_O; - } - - // window.MouseEvent as constructor - try { - new MouseEvent('_', {}); - } catch (o_O) { - /* jshint -W022 */ - o_O = (function ($MouseEvent) { - function MouseEvent(type, init) { - enoughArguments(arguments.length, 'MouseEvent'); - var out = document.createEvent('MouseEvent'); - if (!init) init = {}; - out.initMouseEvent( - type, - !!init.bubbles, - !!init.cancelable, - init.view || window, - init.detail || 1, - init.screenX || 0, - init.screenY || 0, - init.clientX || 0, - init.clientY || 0, - !!init.ctrlKey, - !!init.altKey, - !!init.shiftKey, - !!init.metaKey, - init.button || 0, - init.relatedTarget || null - ); - return out; - } - MouseEvent.prototype = $MouseEvent.prototype; - return MouseEvent; - })(window.MouseEvent || function MouseEvent() {}); - defineProperty(window, 'MouseEvent', { value: o_O }); - // Android 4 gotcha - if (MouseEvent !== o_O) MouseEvent = o_O; - } - - if (!document.querySelectorAll('*').forEach) { - (function () { - function patch(what) { - var querySelectorAll = what.querySelectorAll; - what.querySelectorAll = function qSA(css) { - var result = querySelectorAll.call(this, css); - result.forEach = Array.prototype.forEach; - return result; - }; - } - patch(document); - patch(Element.prototype); - })(); - } - - try { - // https://drafts.csswg.org/selectors-4/#the-scope-pseudo - document.querySelector(':scope *'); - } catch (o_O) { - (function () { - var dataScope = 'data-scope-' + ((Math.random() * 1e9) >>> 0); - var proto = Element.prototype; - var querySelector = proto.querySelector; - var querySelectorAll = proto.querySelectorAll; - proto.querySelector = function qS(css) { - return find(this, querySelector, css); - }; - proto.querySelectorAll = function qSA(css) { - return find(this, querySelectorAll, css); - }; - function find(node, method, css) { - node.setAttribute(dataScope, null); - var result = method.call( - node, - String(css).replace(/(^|,\s*)(:scope([ >]|$))/g, function ($0, $1, $2, $3) { - return $1 + '[' + dataScope + ']' + ($3 || ' '); - }) - ); - node.removeAttribute(dataScope); - return result; - } - })(); - } -})(window); -(function (global) { - 'use strict'; - - // a WeakMap fallback for DOM nodes only used as key - var DOMMap = - global.WeakMap || - (function () { - var counter = 0, - dispatched = false, - drop = false, - value; - - function dispatch(key, ce, shouldDrop) { - drop = shouldDrop; - dispatched = false; - value = undefined; - key.dispatchEvent(ce); - } - - function Handler(value) { - this.value = value; - } - - Handler.prototype.handleEvent = function handleEvent(e) { - dispatched = true; - if (drop) { - e.currentTarget.removeEventListener(e.type, this, false); - } else { - value = this.value; - } - }; - - function DOMMap() { - counter++; // make id clashing highly improbable - this.__ce__ = new Event('@DOMMap:' + counter + Math.random()); - } - - DOMMap.prototype = { - 'constructor': DOMMap, - 'delete': function del(key) { - return dispatch(key, this.__ce__, true), dispatched; - }, - 'get': function get(key) { - dispatch(key, this.__ce__, false); - var v = value; - value = undefined; - return v; - }, - 'has': function has(key) { - return dispatch(key, this.__ce__, false), dispatched; - }, - 'set': function set(key, value) { - dispatch(key, this.__ce__, true); - key.addEventListener(this.__ce__.type, new Handler(value), false); - return this; - }, - }; - - return DOMMap; - })(); - - function Dict() {} - Dict.prototype = (Object.create || Object)(null); - - // https://dom.spec.whatwg.org/#interface-eventtarget - - function createEventListener(type, callback, options) { - function eventListener(e) { - if (eventListener.once) { - e.currentTarget.removeEventListener(e.type, callback, eventListener); - eventListener.removed = true; - } - if (eventListener.passive) { - e.preventDefault = createEventListener.preventDefault; - } - if (typeof eventListener.callback === 'function') { - /* jshint validthis: true */ - eventListener.callback.call(this, e); - } else if (eventListener.callback) { - eventListener.callback.handleEvent(e); - } - if (eventListener.passive) { - delete e.preventDefault; - } - } - eventListener.type = type; - eventListener.callback = callback; - eventListener.capture = !!options.capture; - eventListener.passive = !!options.passive; - eventListener.once = !!options.once; - // currently pointless but specs say to use it, so ... - eventListener.removed = false; - return eventListener; - } - - createEventListener.preventDefault = function preventDefault() {}; - - var Event = global.CustomEvent, - dE = global.dispatchEvent, - aEL = global.addEventListener, - rEL = global.removeEventListener, - counter = 0, - increment = function () { - counter++; - }, - indexOf = - [].indexOf || - function indexOf(value) { - var length = this.length; - while (length--) { - if (this[length] === value) { - break; - } - } - return length; - }, - getListenerKey = function (options) { - return ''.concat(options.capture ? '1' : '0', options.passive ? '1' : '0', options.once ? '1' : '0'); - }, - augment; - - try { - aEL('_', increment, { once: true }); - dE(new Event('_')); - dE(new Event('_')); - rEL('_', increment, { once: true }); - } catch (o_O) {} - - if (counter !== 1) { - (function () { - var dm = new DOMMap(); - function createAEL(aEL) { - return function addEventListener(type, handler, options) { - if (options && typeof options !== 'boolean') { - var info = dm.get(this), - key = getListenerKey(options), - i, - tmp, - wrap; - if (!info) dm.set(this, (info = new Dict())); - if (!(type in info)) - info[type] = { - handler: [], - wrap: [], - }; - tmp = info[type]; - i = indexOf.call(tmp.handler, handler); - if (i < 0) { - i = tmp.handler.push(handler) - 1; - tmp.wrap[i] = wrap = new Dict(); - } else { - wrap = tmp.wrap[i]; - } - if (!(key in wrap)) { - wrap[key] = createEventListener(type, handler, options); - aEL.call(this, type, wrap[key], wrap[key].capture); - } - } else { - aEL.call(this, type, handler, options); - } - }; - } - function createREL(rEL) { - return function removeEventListener(type, handler, options) { - if (options && typeof options !== 'boolean') { - var info = dm.get(this), - key, - i, - tmp, - wrap; - if (info && type in info) { - tmp = info[type]; - i = indexOf.call(tmp.handler, handler); - if (-1 < i) { - key = getListenerKey(options); - wrap = tmp.wrap[i]; - if (key in wrap) { - rEL.call(this, type, wrap[key], wrap[key].capture); - delete wrap[key]; - // return if there are other wraps - for (key in wrap) return; - // otherwise remove all the things - tmp.handler.splice(i, 1); - tmp.wrap.splice(i, 1); - // if there are no other handlers - if (tmp.handler.length === 0) - // drop the info[type] entirely - delete info[type]; - } - } - } - } else { - rEL.call(this, type, handler, options); - } - }; - } - - augment = function (Constructor) { - if (!Constructor) return; - var proto = Constructor.prototype; - proto.addEventListener = createAEL(proto.addEventListener); - proto.removeEventListener = createREL(proto.removeEventListener); - }; - - if (global.EventTarget) { - augment(EventTarget); - } else { - augment(global.Text); - augment(global.Element || global.HTMLElement); - augment(global.HTMLDocument); - augment(global.Window || { prototype: global }); - augment(global.XMLHttpRequest); - } - })(); - } -})(window); diff --git a/tgui/packages/tgui-polyfill/03-css-om.js b/tgui/packages/tgui-polyfill/03-css-om.js deleted file mode 100644 index 534265e6f7..0000000000 --- a/tgui/packages/tgui-polyfill/03-css-om.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * CSS Object Model patches - * - * Adapted from: https://github.com/shawnbot/aight - * - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -/* eslint-disable */ -(function (Proto) { - 'use strict'; - - if (typeof Proto.setAttribute !== 'undefined') { - function toAttr(prop) { - return prop.replace(/-[a-z]/g, function (bit) { - return bit[1].toUpperCase(); - }); - } - - Proto.setProperty = function (prop, value) { - var attr = toAttr(prop); - if (!value) { - return this.removeAttribute(attr); - } - var str = String(value); - return this.setAttribute(attr, str); - }; - - Proto.getPropertyValue = function (prop) { - var attr = toAttr(prop); - return this.getAttribute(attr) || null; - }; - - Proto.removeProperty = function (prop) { - var attr = toAttr(prop); - var value = this.getAttribute(attr); - this.removeAttribute(attr); - return value; - }; - } -})(CSSStyleDeclaration.prototype); diff --git a/tgui/packages/tgui-polyfill/1-misc.js b/tgui/packages/tgui-polyfill/1-misc.js new file mode 100644 index 0000000000..cefbdbdbdf --- /dev/null +++ b/tgui/packages/tgui-polyfill/1-misc.js @@ -0,0 +1,48 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +/* eslint-disable */ +(function () { + 'use strict'; + + // ie11 polyfills + !(function () { + // append + function t() { + var e = Array.prototype.slice.call(arguments), + n = document.createDocumentFragment(); + e.forEach(function (e) { + var t = e instanceof Node; + n.appendChild(t ? e : document.createTextNode(String(e))); + }), + this.appendChild(n); + } + // remove + function n() { + this.parentNode && this.parentNode.removeChild(this); + } + + // add to prototype + [Element.prototype, Document.prototype, DocumentFragment.prototype].forEach( + function (e) { + e.hasOwnProperty('append') || + Object.defineProperty(e, 'append', { + configurable: !0, + enumerable: !0, + writable: !0, + value: t, + }); + e.hasOwnProperty('remove') || + Object.defineProperty(e, 'remove', { + configurable: !0, + enumerable: !0, + writable: !0, + value: n, + }); + } + ); + })(); +})(); diff --git a/tgui/packages/tgui-polyfill/10-misc.js b/tgui/packages/tgui-polyfill/10-misc.js deleted file mode 100644 index f2d69f288c..0000000000 --- a/tgui/packages/tgui-polyfill/10-misc.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -/* eslint-disable */ -(function () { - 'use strict'; - - // Necessary polyfill to make Webpack code splitting work on IE8 - if (!Function.prototype.bind) - (function () { - var slice = Array.prototype.slice; - Function.prototype.bind = function () { - var thatFunc = this, - thatArg = arguments[0]; - var args = slice.call(arguments, 1); - if (typeof thatFunc !== 'function') { - // closest thing possible to the ECMAScript 5 - // internal IsCallable function - throw new TypeError('Function.prototype.bind - ' + 'what is trying to be bound is not callable'); - } - return function () { - var funcArgs = args.concat(slice.call(arguments)); - return thatFunc.apply(thatArg, funcArgs); - }; - }; - })(); - - if (!Array.prototype['forEach']) { - Array.prototype.forEach = function (callback, thisArg) { - if (this == null) { - throw new TypeError('Array.prototype.forEach called on null or undefined'); - } - var T, k; - var O = Object(this); - var len = O.length >>> 0; - if (typeof callback !== 'function') { - throw new TypeError(callback + ' is not a function'); - } - if (arguments.length > 1) { - T = thisArg; - } - k = 0; - while (k < len) { - var kValue; - if (k in O) { - kValue = O[k]; - callback.call(T, kValue, k, O); - } - k++; - } - }; - } - - // Inferno needs Int32Array, and it is not covered by core-js. - if (!window.Int32Array) { - window.Int32Array = Array; - } -})(); diff --git a/tgui/packages/tgui-polyfill/package.json b/tgui/packages/tgui-polyfill/package.json index a2f060fcc5..8e43bb8722 100644 --- a/tgui/packages/tgui-polyfill/package.json +++ b/tgui/packages/tgui-polyfill/package.json @@ -1,16 +1,16 @@ { "private": true, "name": "tgui-polyfill", - "version": "4.3.0", + "version": "5.0.0", "scripts": { - "tgui-polyfill:build": "terser 00-html5shiv.js 01-ie8.js 02-dom4.js 03-css-om.js 10-misc.js --ie8 -f ascii_only,comments=false -o ../../public/tgui-polyfill.min.js" + "tgui-polyfill:build": "terser 1-misc.js -f ascii_only,comments=false -o ../../public/tgui-polyfill.min.js" }, "dependencies": { - "core-js": "^3.22.5", - "regenerator-runtime": "^0.13.9", - "unfetch": "^4.2.0" + "core-js": "^3.33.3", + "regenerator-runtime": "^0.14.0", + "unfetch": "^5.0.0" }, "devDependencies": { - "terser": "^5.14.2" + "terser": "^5.24.0" } } diff --git a/tgui/packages/tgui/assets.ts b/tgui/packages/tgui/assets.ts index e519d0c2f7..f62bf652de 100644 --- a/tgui/packages/tgui/assets.ts +++ b/tgui/packages/tgui/assets.ts @@ -4,10 +4,10 @@ * @license MIT */ -import { Action, AnyAction, Middleware } from '../common/redux'; - import { Dispatch } from 'common/redux'; +import { Action, AnyAction, Middleware } from '../common/redux'; + const EXCLUDED_PATTERNS = [/v4shim/i]; const loadedMappings: Record = {}; diff --git a/tgui/packages/tgui/backend.ts b/tgui/packages/tgui/backend.ts index 4a45529c65..2a79354c9b 100644 --- a/tgui/packages/tgui/backend.ts +++ b/tgui/packages/tgui/backend.ts @@ -13,6 +13,7 @@ import { perf } from 'common/perf'; import { createAction } from 'common/redux'; + import { setupDrag } from './drag'; import { globalEvents } from './events'; import { focusMap } from './focus'; @@ -228,7 +229,7 @@ export const backendMiddleware = (store) => { if (process.env.NODE_ENV !== 'production') { logger.log( 'visible in', - perf.measure('render/finish', 'resume/finish') + perf.measure('render/finish', 'resume/finish'), ); } }); @@ -327,7 +328,7 @@ type StateWithSetter = [T, (nextState: T) => void]; */ export const useLocalState = ( key: string, - initialState: T + initialState: T, ): StateWithSetter => { const state = globalStore?.getState()?.backend; const sharedStates = state?.shared ?? {}; @@ -342,7 +343,7 @@ export const useLocalState = ( typeof nextState === 'function' ? nextState(sharedState) : nextState, - }) + }), ); }, ]; @@ -364,7 +365,7 @@ export const useLocalState = ( */ export const useSharedState = ( key: string, - initialState: T + initialState: T, ): StateWithSetter => { const state = globalStore?.getState()?.backend; const sharedStates = state?.shared ?? {}; @@ -377,9 +378,19 @@ export const useSharedState = ( key, value: JSON.stringify( - typeof nextState === 'function' ? nextState(sharedState) : nextState + typeof nextState === 'function' + ? nextState(sharedState) + : nextState, ) || '', }); }, ]; }; + +export const useDispatch = () => { + return globalStore.dispatch; +}; + +export const useSelector = (selector: (state: any) => any) => { + return selector(globalStore?.getState()); +}; diff --git a/tgui/packages/tgui/components/AnimatedNumber.tsx b/tgui/packages/tgui/components/AnimatedNumber.tsx index 53bdba90ab..435ed992d3 100644 --- a/tgui/packages/tgui/components/AnimatedNumber.tsx +++ b/tgui/packages/tgui/components/AnimatedNumber.tsx @@ -5,7 +5,7 @@ */ import { clamp, toFixed } from 'common/math'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; const isSafeNumber = (value: number) => { // prettier-ignore @@ -100,8 +100,6 @@ export class AnimatedNumber extends Component { this.startTicking(); } - // We render the inner `span` directly using a ref to bypass inferno diffing - // and reach 60 frames per second--tell inferno not to re-render this tree. return false; } @@ -157,7 +155,6 @@ export class AnimatedNumber extends Component { } if (this.ref.current) { - // Directly update the inner span, without bothering inferno. this.ref.current.textContent = this.getText(); } } diff --git a/tgui/packages/tgui/components/Autofocus.tsx b/tgui/packages/tgui/components/Autofocus.tsx index 28945dd7aa..a0b3f6f765 100644 --- a/tgui/packages/tgui/components/Autofocus.tsx +++ b/tgui/packages/tgui/components/Autofocus.tsx @@ -1,19 +1,17 @@ -import { Component, createRef } from 'inferno'; +import { createRef, PropsWithChildren, useEffect } from 'react'; -export class Autofocus extends Component { - ref = createRef(); +export const Autofocus = (props: PropsWithChildren) => { + const ref = createRef(); - componentDidMount() { + useEffect(() => { setTimeout(() => { - this.ref.current?.focus(); + ref.current?.focus(); }, 1); - } + }, []); - render() { - return ( -
- {this.props.children} -
- ); - } -} + return ( +
+ {props.children} +
+ ); +}; diff --git a/tgui/packages/tgui/components/Blink.jsx b/tgui/packages/tgui/components/Blink.jsx index bd781336b4..7d7bb16170 100644 --- a/tgui/packages/tgui/components/Blink.jsx +++ b/tgui/packages/tgui/components/Blink.jsx @@ -1,11 +1,11 @@ -import { Component } from 'inferno'; +import { Component } from 'react'; const DEFAULT_BLINKING_INTERVAL = 1000; const DEFAULT_BLINKING_TIME = 1000; export class Blink extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.state = { hidden: false, }; @@ -60,7 +60,8 @@ export class Blink extends Component { + }} + > {props.children} ); diff --git a/tgui/packages/tgui/components/BodyZoneSelector.tsx b/tgui/packages/tgui/components/BodyZoneSelector.tsx index cf8dc430e2..39f7aa4073 100644 --- a/tgui/packages/tgui/components/BodyZoneSelector.tsx +++ b/tgui/packages/tgui/components/BodyZoneSelector.tsx @@ -1,4 +1,5 @@ -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; + import { resolveAsset } from '../assets'; import { Box } from './Box'; @@ -84,7 +85,8 @@ export class BodyZoneSelector extends Component< width: `${32 * scale}px`, height: `${32 * scale}px`, position: 'relative', - }}> + }} + > @@ -124,11 +125,10 @@ export class BodyZoneSelector extends Component< as="img" src={resolveAsset(`body_zones.${selectedZone}.png`)} style={{ - '-ms-interpolation-mode': 'nearest-neighbor', - 'pointer-events': 'none', - 'position': 'absolute', - 'width': `${32 * scale}px`, - 'height': `${32 * scale}px`, + pointerEvents: 'none', + position: 'absolute', + width: `${32 * scale}px`, + height: `${32 * scale}px`, }} /> )} @@ -138,12 +138,11 @@ export class BodyZoneSelector extends Component< as="img" src={resolveAsset(`body_zones.${hoverZone}.png`)} style={{ - '-ms-interpolation-mode': 'nearest-neighbor', - 'opacity': 0.5, - 'pointer-events': 'none', - 'position': 'absolute', - 'width': `${32 * scale}px`, - 'height': `${32 * scale}px`, + opacity: 0.5, + pointerEvents: 'none', + position: 'absolute', + width: `${32 * scale}px`, + height: `${32 * scale}px`, }} /> )} diff --git a/tgui/packages/tgui/components/Box.tsx b/tgui/packages/tgui/components/Box.tsx index 96244d5bd7..5c5acb63b3 100644 --- a/tgui/packages/tgui/components/Box.tsx +++ b/tgui/packages/tgui/components/Box.tsx @@ -4,16 +4,16 @@ * @license MIT */ -import { BooleanLike, classes, pureComponentHooks } from 'common/react'; -import { createVNode, InfernoNode, SFC } from 'inferno'; -import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; +import { BooleanLike, classes } from 'common/react'; +import { createElement, ReactNode } from 'react'; + import { CSS_COLORS } from '../constants'; export type BoxProps = { [key: string]: any; as?: string; className?: string | BooleanLike; - children?: InfernoNode; + children?: ReactNode; position?: string | BooleanLike; overflow?: string | BooleanLike; overflowX?: string | BooleanLike; @@ -73,15 +73,12 @@ export type BoxProps = { export const unit = (value: unknown): string | undefined => { if (typeof value === 'string') { // Transparently convert pixels into rem units - if (value.endsWith('px') && !Byond.IS_LTE_IE8) { + if (value.endsWith('px')) { return parseFloat(value) / 12 + 'rem'; } return value; } if (typeof value === 'number') { - if (Byond.IS_LTE_IE8) { - return value * 12 + 'px'; - } return value + 'rem'; } }; @@ -136,71 +133,69 @@ const mapColorPropTo = (attrName) => (style, value) => { } }; -const styleMapperByPropName = { - // Direct mapping - position: mapRawPropTo('position'), - overflow: mapRawPropTo('overflow'), - overflowX: mapRawPropTo('overflow-x'), - overflowY: mapRawPropTo('overflow-y'), - top: mapUnitPropTo('top', unit), +// String / number props +const stringStyleMap = { bottom: mapUnitPropTo('bottom', unit), - left: mapUnitPropTo('left', unit), - right: mapUnitPropTo('right', unit), - width: mapUnitPropTo('width', unit), - minWidth: mapUnitPropTo('min-width', unit), - maxWidth: mapUnitPropTo('max-width', unit), + fontFamily: mapRawPropTo('fontFamily'), + fontSize: mapUnitPropTo('fontSize', unit), height: mapUnitPropTo('height', unit), - minHeight: mapUnitPropTo('min-height', unit), - maxHeight: mapUnitPropTo('max-height', unit), - fontSize: mapUnitPropTo('font-size', unit), - fontFamily: mapRawPropTo('font-family'), + left: mapUnitPropTo('left', unit), + maxHeight: mapUnitPropTo('maxHeight', unit), + maxWidth: mapUnitPropTo('maxWidth', unit), + minHeight: mapUnitPropTo('minHeight', unit), + minWidth: mapUnitPropTo('minWidth', unit), + opacity: mapRawPropTo('opacity'), + overflow: mapRawPropTo('overflow'), + overflowX: mapRawPropTo('overflowX'), + overflowY: mapRawPropTo('overflowY'), + position: mapRawPropTo('position'), + right: mapUnitPropTo('right', unit), + textAlign: mapRawPropTo('textAlign'), + top: mapUnitPropTo('top', unit), + verticalAlign: mapRawPropTo('verticalAlign'), + width: mapUnitPropTo('width', unit), + lineHeight: (style, value) => { if (typeof value === 'number') { - style['line-height'] = value; + style['lineHeight'] = value; } else if (typeof value === 'string') { - style['line-height'] = unit(value); + style['lineHeight'] = unit(value); } }, - opacity: mapRawPropTo('opacity'), - textAlign: mapRawPropTo('text-align'), - verticalAlign: mapRawPropTo('vertical-align'), + textTransform: mapRawPropTo('text-transform'), // VOREStation Addition - // Boolean props - inline: mapBooleanPropTo('display', 'inline-block'), - bold: mapBooleanPropTo('font-weight', 'bold'), - italic: mapBooleanPropTo('font-style', 'italic'), - nowrap: mapBooleanPropTo('white-space', 'nowrap'), - preserveWhitespace: mapBooleanPropTo('white-space', 'pre-wrap'), + // Margins m: mapDirectionalUnitPropTo('margin', halfUnit, [ - 'top', - 'bottom', - 'left', - 'right', + 'Top', + 'Bottom', + 'Left', + 'Right', ]), - mx: mapDirectionalUnitPropTo('margin', halfUnit, ['left', 'right']), - my: mapDirectionalUnitPropTo('margin', halfUnit, ['top', 'bottom']), - mt: mapUnitPropTo('margin-top', halfUnit), - mb: mapUnitPropTo('margin-bottom', halfUnit), - ml: mapUnitPropTo('margin-left', halfUnit), - mr: mapUnitPropTo('margin-right', halfUnit), - // Margins + mx: mapDirectionalUnitPropTo('margin', halfUnit, ['Left', 'Right']), + my: mapDirectionalUnitPropTo('margin', halfUnit, ['Top', 'Bottom']), + mt: mapUnitPropTo('marginTop', halfUnit), + mb: mapUnitPropTo('marginBottom', halfUnit), + ml: mapUnitPropTo('marginLeft', halfUnit), + mr: mapUnitPropTo('marginRight', halfUnit), + // Padding p: mapDirectionalUnitPropTo('padding', halfUnit, [ - 'top', - 'bottom', - 'left', - 'right', + 'Top', + 'Bottom', + 'Left', + 'Right', ]), - px: mapDirectionalUnitPropTo('padding', halfUnit, ['left', 'right']), - py: mapDirectionalUnitPropTo('padding', halfUnit, ['top', 'bottom']), - pt: mapUnitPropTo('padding-top', halfUnit), - pb: mapUnitPropTo('padding-bottom', halfUnit), - pl: mapUnitPropTo('padding-left', halfUnit), - pr: mapUnitPropTo('padding-right', halfUnit), + px: mapDirectionalUnitPropTo('padding', halfUnit, ['Left', 'Right']), + py: mapDirectionalUnitPropTo('padding', halfUnit, ['Top', 'Bottom']), + pt: mapUnitPropTo('paddingTop', halfUnit), + pb: mapUnitPropTo('paddingBottom', halfUnit), + pl: mapUnitPropTo('paddingLeft', halfUnit), + pr: mapUnitPropTo('paddingRight', halfUnit), // Color props color: mapColorPropTo('color'), textColor: mapColorPropTo('color'), - backgroundColor: mapColorPropTo('background-color'), + backgroundColor: mapColorPropTo('backgroundColor'), + // VOREStation Addition Start // Flex props flexGrow: mapRawPropTo('flex-grow'), @@ -208,6 +203,7 @@ const styleMapperByPropName = { flexBasis: mapRawPropTo('flex-basis'), flex: mapRawPropTo('flex'), // VOREStation Addition End + // Utility props fillPositionedParent: (style, value) => { if (value) { @@ -218,44 +214,42 @@ const styleMapperByPropName = { style['right'] = 0; } }, -}; +} as const; + +// Boolean props +const booleanStyleMap = { + bold: mapBooleanPropTo('fontWeight', 'bold'), + inline: mapBooleanPropTo('display', 'inline-block'), + italic: mapBooleanPropTo('fontStyle', 'italic'), + nowrap: mapBooleanPropTo('whiteSpace', 'nowrap'), + preserveWhitespace: mapBooleanPropTo('whiteSpace', 'pre-wrap'), +} as const; + +export const computeBoxProps = (props) => { + const computedProps: Record = {}; + const computedStyles: Record = {}; -export const computeBoxProps = (props: BoxProps) => { - const computedProps: HTMLAttributes = {}; - const computedStyles = {}; // Compute props for (let propName of Object.keys(props)) { if (propName === 'style') { continue; } - // IE8: onclick workaround - if (Byond.IS_LTE_IE8 && propName === 'onClick') { - computedProps.onclick = props[propName]; - continue; - } + const propValue = props[propName]; - const mapPropToStyle = styleMapperByPropName[propName]; + + const mapPropToStyle = + stringStyleMap[propName] || booleanStyleMap[propName]; + if (mapPropToStyle) { mapPropToStyle(computedStyles, propValue); } else { computedProps[propName] = propValue; } } - // Concatenate styles - let style = ''; - for (let attrName of Object.keys(computedStyles)) { - const attrValue = computedStyles[attrName]; - style += attrName + ':' + attrValue + ';'; - } - if (props.style) { - for (let attrName of Object.keys(props.style)) { - const attrValue = props.style[attrName]; - style += attrName + ':' + attrValue + ';'; - } - } - if (style.length > 0) { - computedProps.style = style; - } + + // Merge computed styles and any directly provided styles + computedProps.style = { ...computedStyles, ...props.style }; + return computedProps; }; @@ -268,27 +262,25 @@ export const computeBoxClassName = (props: BoxProps) => { ]); }; -export const Box: SFC = (props: BoxProps) => { +export const Box = (props: BoxProps) => { const { as = 'div', className, children, ...rest } = props; - // Render props - if (typeof children === 'function') { - return children(computeBoxProps(props)); - } - const computedClassName = - typeof className === 'string' - ? className + ' ' + computeBoxClassName(rest) - : computeBoxClassName(rest); + + // Compute class name and styles + const computedClassName = className + ? `${className} ${computeBoxClassName(rest)}` + : computeBoxClassName(rest); const computedProps = computeBoxProps(rest); - // Render a wrapper element - return createVNode( - VNodeFlags.HtmlElement, - as, - computedClassName, + if (as === 'img') { + computedProps.style['-ms-interpolation-mode'] = 'nearest-neighbor'; + } + + // Render the component + return createElement( + typeof as === 'string' ? as : 'div', + { + ...computedProps, + className: computedClassName, + }, children, - ChildFlags.UnknownChildren, - computedProps, - undefined ); }; - -Box.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui/components/Button.jsx b/tgui/packages/tgui/components/Button.jsx index 58d879d228..e352df2f8f 100644 --- a/tgui/packages/tgui/components/Button.jsx +++ b/tgui/packages/tgui/components/Button.jsx @@ -5,15 +5,12 @@ */ import { KEY_ENTER, KEY_ESCAPE, KEY_SPACE } from 'common/keycodes'; -import { classes, pureComponentHooks } from 'common/react'; -import { Component, createRef } from 'inferno'; -import { createLogger } from '../logging'; +import { classes } from 'common/react'; +import { Component, createRef } from 'react'; import { Box, computeBoxClassName, computeBoxProps } from './Box'; import { Icon } from './Icon'; import { Tooltip } from './Tooltip'; -const logger = createLogger('Button'); - export const Button = (props) => { const { className, @@ -34,30 +31,18 @@ export const Button = (props) => { circular, content, children, - onclick, onClick, verticalAlignContent, ...rest } = props; const hasContent = !!(content || children); - // A warning about the lowercase onclick - if (onclick) { - logger.warn( - `Lowercase 'onclick' is not supported on Button and lowercase` + - ` prop names are discouraged in general. Please use a camelCase` + - `'onClick' instead and read: ` + - `https://infernojs.org/docs/guides/event-handling` - ); - } + rest.onClick = (e) => { if (!disabled && onClick) { onClick(e); } }; - // IE8: Use "unselectable" because "user-select" doesn't work. - if (Byond.IS_LTE_IE8) { - rest.unselectable = true; - } + let buttonContent = (
{ return; } }} - {...computeBoxProps(rest)}> + {...computeBoxProps(rest)} + >
{icon && iconPosition !== 'right' && ( { return buttonContent; }; -Button.defaultHooks = pureComponentHooks; - export const ButtonCheckbox = (props) => { const { checked, ...rest } = props; return ( @@ -153,8 +137,8 @@ export const ButtonCheckbox = (props) => { Button.Checkbox = ButtonCheckbox; export class ButtonConfirm extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.state = { clickedOnce: false, }; @@ -204,8 +188,8 @@ export class ButtonConfirm extends Component { Button.Confirm = ButtonConfirm; export class ButtonInput extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.inputRef = createRef(); this.state = { inInput: false, @@ -267,15 +251,16 @@ export class ButtonInput extends Component { 'Button--color--' + color, ])} {...rest} - onClick={() => this.setInInput(true)}> + onClick={() => this.setInInput(true)} + > {icon && }
{content}
{ if (!this.state.inInput) { @@ -313,8 +298,8 @@ export class ButtonInput extends Component { Button.Input = ButtonInput; export class ButtonFile extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.inputRef = createRef(); } diff --git a/tgui/packages/tgui/components/ByondUi.jsx b/tgui/packages/tgui/components/ByondUi.jsx index 4623fe5770..2914bb8905 100644 --- a/tgui/packages/tgui/components/ByondUi.jsx +++ b/tgui/packages/tgui/components/ByondUi.jsx @@ -6,7 +6,7 @@ import { shallowDiffers } from 'common/react'; import { debounce } from 'common/timer'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { createLogger } from '../logging'; import { computeBoxProps } from './Box'; @@ -92,20 +92,12 @@ export class ByondUi extends Component { } componentDidMount() { - // IE8: It probably works, but fuck you anyway. - if (Byond.IS_LTE_IE10) { - return; - } window.addEventListener('resize', this.handleResize); this.componentDidUpdate(); this.handleResize(); } componentDidUpdate() { - // IE8: It probably works, but fuck you anyway. - if (Byond.IS_LTE_IE10) { - return; - } const { params = {} } = this.props; const box = getBoundingBox(this.containerRef.current); logger.debug('bounding box', box); @@ -118,10 +110,6 @@ export class ByondUi extends Component { } componentWillUnmount() { - // IE8: It probably works, but fuck you anyway. - if (Byond.IS_LTE_IE10) { - return; - } window.removeEventListener('resize', this.handleResize); this.byondUiElement.unmount(); } @@ -131,7 +119,7 @@ export class ByondUi extends Component { return (
{/* Filler */} -
+
); } diff --git a/tgui/packages/tgui/components/Chart.jsx b/tgui/packages/tgui/components/Chart.tsx similarity index 53% rename from tgui/packages/tgui/components/Chart.jsx rename to tgui/packages/tgui/components/Chart.tsx index fac444bd1d..205dc6fbec 100644 --- a/tgui/packages/tgui/components/Chart.jsx +++ b/tgui/packages/tgui/components/Chart.tsx @@ -5,29 +5,57 @@ */ import { map, zipWith } from 'common/collections'; -import { pureComponentHooks } from 'common/react'; -import { Component, createRef } from 'inferno'; -import { Box } from './Box'; +import { Component, createRef, RefObject } from 'react'; -const normalizeData = (data, scale, rangeX, rangeY) => { +import { Box, BoxProps } from './Box'; + +type Props = { + data: number[][]; +} & Partial<{ + fillColor: string; + rangeX: [number, number]; + rangeY: [number, number]; + strokeColor: string; + strokeWidth: number; +}> & + BoxProps; + +type State = { + viewBox: [number, number]; +}; + +type Point = number[]; +type Range = [number, number]; + +const normalizeData = ( + data: Point[], + scale: number[], + rangeX?: Range, + rangeY?: Range, +) => { if (data.length === 0) { return []; } + const min = zipWith(Math.min)(...data); const max = zipWith(Math.max)(...data); + if (rangeX !== undefined) { min[0] = rangeX[0]; max[0] = rangeX[1]; } + if (rangeY !== undefined) { min[1] = rangeY[0]; max[1] = rangeY[1]; } - const normalized = map((point) => { - return zipWith((value, min, max, scale) => { + + const normalized = map((point: Point) => { + return zipWith((value: number, min: number, max: number, scale: number) => { return ((value - min) / (max - min)) * scale; })(point, min, max, scale); })(data); + return normalized; }; @@ -40,20 +68,18 @@ const dataToPolylinePoints = (data) => { return points; }; -class LineChart extends Component { - constructor(props) { +class LineChart extends Component { + ref: RefObject; + state: State; + + constructor(props: Props) { super(props); this.ref = createRef(); this.state = { // Initial guess viewBox: [600, 200], }; - this.handleResize = () => { - const element = this.ref.current; - this.setState({ - viewBox: [element.offsetWidth, element.offsetHeight], - }); - }; + this.handleResize = this.handleResize.bind(this); } componentDidMount() { @@ -65,6 +91,16 @@ class LineChart extends Component { window.removeEventListener('resize', this.handleResize); } + handleResize = () => { + const element = this.ref.current; + if (!element) { + return; + } + this.setState({ + viewBox: [element.offsetWidth, element.offsetHeight], + }); + }; + render() { const { data = [], @@ -87,41 +123,37 @@ class LineChart extends Component { normalized.push([-strokeWidth, first[1]]); } const points = dataToPolylinePoints(normalized); + const divProps = { ...rest, className: '', ref: this.ref }; + return ( - {(props) => ( -
- - - -
- )} + + + + +
); } } -LineChart.defaultHooks = pureComponentHooks; - -const Stub = (props) => null; - -// IE8: No inline svg support export const Chart = { - Line: Byond.IS_LTE_IE8 ? Stub : LineChart, + Line: LineChart, }; diff --git a/tgui/packages/tgui/components/Collapsible.jsx b/tgui/packages/tgui/components/Collapsible.jsx index f91eeddb45..9fda360b0a 100644 --- a/tgui/packages/tgui/components/Collapsible.jsx +++ b/tgui/packages/tgui/components/Collapsible.jsx @@ -4,7 +4,7 @@ * @license MIT */ -import { Component } from 'inferno'; +import { Component } from 'react'; import { Box } from './Box'; import { Button } from './Button'; @@ -30,7 +30,8 @@ export class Collapsible extends Component { color={color} icon={open ? 'chevron-down' : 'chevron-right'} onClick={() => this.setState({ open: !open })} - {...rest}> + {...rest} + > {title}
diff --git a/tgui/packages/tgui/components/ColorBox.jsx b/tgui/packages/tgui/components/ColorBox.jsx index a6203ca469..48916efa9b 100644 --- a/tgui/packages/tgui/components/ColorBox.jsx +++ b/tgui/packages/tgui/components/ColorBox.jsx @@ -4,7 +4,7 @@ * @license MIT */ -import { classes, pureComponentHooks } from 'common/react'; +import { classes } from 'common/react'; import { computeBoxClassName, computeBoxProps } from './Box'; export const ColorBox = (props) => { @@ -22,10 +22,9 @@ export const ColorBox = (props) => { return (
+ {...computeBoxProps(rest)} + > {content || '.'}
); }; - -ColorBox.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui/components/Dialog.tsx b/tgui/packages/tgui/components/Dialog.tsx index 277a03b284..897ce236a4 100644 --- a/tgui/packages/tgui/components/Dialog.tsx +++ b/tgui/packages/tgui/components/Dialog.tsx @@ -52,7 +52,8 @@ const DialogButton = (props: DialogButtonProps) => { ); diff --git a/tgui/packages/tgui/components/DraggableControl.jsx b/tgui/packages/tgui/components/DraggableControl.jsx index 8ada6f2fa4..7a8671c17f 100644 --- a/tgui/packages/tgui/components/DraggableControl.jsx +++ b/tgui/packages/tgui/components/DraggableControl.jsx @@ -5,8 +5,7 @@ */ import { clamp } from 'common/math'; -import { pureComponentHooks } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { AnimatedNumber } from './AnimatedNumber'; const DEFAULT_UPDATE_RATE = 400; @@ -97,13 +96,13 @@ export class DraggableControl extends Component { state.internalValue = clamp( state.internalValue + (offset * step) / stepPixelSize, minValue - step, - maxValue + step + maxValue + step, ); // Clamp the final value state.value = clamp( state.internalValue - (state.internalValue % step) + stepOffset, minValue, - maxValue + maxValue, ); state.origin = getScalarScreenOffset(e, dragMatrix); } else if (Math.abs(offset) > 4) { @@ -196,8 +195,8 @@ export class DraggableControl extends Component { style={{ display: !editing ? 'none' : undefined, height: height, - 'line-height': lineHeight, - 'font-size': fontSize, + lineHeight: lineHeight, + fontsize: fontSize, }} onBlur={(e) => { if (!editing) { @@ -276,7 +275,6 @@ export class DraggableControl extends Component { } } -DraggableControl.defaultHooks = pureComponentHooks; DraggableControl.defaultProps = { minValue: -Infinity, maxValue: +Infinity, diff --git a/tgui/packages/tgui/components/Dropdown.tsx b/tgui/packages/tgui/components/Dropdown.tsx index 7fa0e3ae8d..4fabfbf0bb 100644 --- a/tgui/packages/tgui/components/Dropdown.tsx +++ b/tgui/packages/tgui/components/Dropdown.tsx @@ -1,386 +1,220 @@ -import { createPopper, VirtualElement } from '@popperjs/core'; import { classes } from 'common/react'; -import { Component, findDOMfromVNode, InfernoNode, render } from 'inferno'; -import { Box, BoxProps } from './Box'; +import { ReactNode, useCallback, useEffect, useRef, useState } from 'react'; + +import { BoxProps, unit } from './Box'; import { Button } from './Button'; import { Icon } from './Icon'; -import { Stack } from './Stack'; +import { Popper } from './Popper'; -export interface DropdownEntry { - displayText: string | number | InfernoNode; - value: string | number | Enumerator; +type DropdownEntry = { + displayText: ReactNode; + value: string | number; +}; + +type DropdownOption = string | DropdownEntry; + +type Props = { + /** An array of strings which will be displayed in the + dropdown when open. See Dropdown.tsx for more advanced usage with DropdownEntry */ + options: DropdownOption[]; + /** Called when a value is picked from the list, `value` is the value that was picked */ + onSelected: (value: any) => void; +} & Partial<{ + /** Whether to display previous / next buttons */ + buttons: boolean; + /** Whether to clip the selected text */ + clipSelectedText: boolean; + /** Color of dropdown button */ + color: string; + /** Disables the dropdown */ + disabled: boolean; + /** Text to always display in place of the selected text */ + displayText: ReactNode; + /** Icon to display in dropdown button */ + icon: string; + /** Angle of the icon */ + iconRotation: number; + /** Whether or not the icon should spin */ + iconSpin: boolean; + /** Width of the dropdown menu. Default: 15rem */ + menuWidth: string; + /** Whether or not the arrow on the right hand side of the dropdown button is visible */ + noChevron: boolean; + /** Called when dropdown button is clicked */ + onClick: (event) => void; + /** Dropdown renders over instead of below */ + over: boolean; + /** Currently selected entry */ + selected: string | number; +}> & + BoxProps; + +function getOptionValue(option: DropdownOption) { + return typeof option === 'string' ? option : option.value; } -type DropdownUniqueProps = { - options: string[] | DropdownEntry[]; - icon?: string; - iconRotation?: number; - clipSelectedText?: boolean; - width?: string; - menuWidth?: string; - over?: boolean; - color?: string; - nochevron?: boolean; - displayText?: string | number | InfernoNode; - onClick?: (event) => void; - // you freaks really are just doing anything with this shit - selected?: any; - onSelected?: (selected: any) => void; - buttons?: boolean; -}; +export function Dropdown(props: Props) { + const { + buttons, + className, + clipSelectedText = true, + color = 'default', + disabled, + displayText, + icon, + iconRotation, + iconSpin, + menuWidth = '15rem', + noChevron, + onClick, + onSelected, + options = [], + over, + selected, + width, + } = props; -export type DropdownProps = BoxProps & DropdownUniqueProps; + const [open, setOpen] = useState(false); + const adjustedOpen = over ? !open : open; + const innerRef = useRef(null); -const DEFAULT_OPTIONS = { - placement: 'left-start', - modifiers: [ - { - name: 'eventListeners', - enabled: false, - }, - ], -}; -const NULL_RECT: DOMRect = { - width: 0, - height: 0, - top: 0, - right: 0, - bottom: 0, - left: 0, - x: 0, - y: 0, - toJSON: () => null, -} as const; - -type DropdownState = { - selected?: string; - open: boolean; -}; - -const DROPDOWN_DEFAULT_CLASSNAMES = 'Layout Dropdown__menu'; -const DROPDOWN_SCROLL_CLASSNAMES = 'Layout Dropdown__menu-scroll'; - -export class Dropdown extends Component { - static renderedMenu: HTMLDivElement | undefined; - static singletonPopper: ReturnType | undefined; - static currentOpenMenu: Element | undefined; - static virtualElement: VirtualElement = { - getBoundingClientRect: () => - Dropdown.currentOpenMenu?.getBoundingClientRect() ?? NULL_RECT, - }; - menuContents: any; - state: DropdownState = { - open: false, - selected: this.props.selected, - }; - - handleClick = () => { - if (this.state.open) { - this.setOpen(false); - } - }; - - getDOMNode() { - return findDOMfromVNode(this.$LI, true); - } - - componentDidMount() { - const domNode = this.getDOMNode(); - - if (!domNode) { - return; - } - } - - openMenu() { - let renderedMenu = Dropdown.renderedMenu; - if (renderedMenu === undefined) { - renderedMenu = document.createElement('div'); - renderedMenu.className = DROPDOWN_DEFAULT_CLASSNAMES; - document.body.appendChild(renderedMenu); - Dropdown.renderedMenu = renderedMenu; - } - - const domNode = this.getDOMNode()!; - Dropdown.currentOpenMenu = domNode; - - renderedMenu.scrollTop = 0; - renderedMenu.style.width = - this.props.menuWidth || - // Hack, but domNode should *always* be the parent control meaning it will have width - // @ts-ignore - `${domNode.offsetWidth}px`; - renderedMenu.style.opacity = '1'; - renderedMenu.style.pointerEvents = 'auto'; - - // ie hack - // ie has this bizarre behavior where focus just silently fails if the - // element being targeted "isn't ready" - // 400 is probably way too high, but the lack of hotloading is testing my - // patience on tuning it - // I'm beyond giving a shit at this point it fucking works whatever - setTimeout(() => { - Dropdown.renderedMenu?.focus(); - }, 400); - this.renderMenuContent(); - } - - closeMenu() { - if (Dropdown.currentOpenMenu !== this.getDOMNode()) { - return; - } - - Dropdown.currentOpenMenu = undefined; - Dropdown.renderedMenu!.style.opacity = '0'; - Dropdown.renderedMenu!.style.pointerEvents = 'none'; - } - - componentWillUnmount() { - this.closeMenu(); - this.setOpen(false); - } - - renderMenuContent() { - const renderedMenu = Dropdown.renderedMenu; - if (!renderedMenu) { - return; - } - if (renderedMenu.offsetHeight > 200) { - renderedMenu.className = DROPDOWN_SCROLL_CLASSNAMES; - } else { - renderedMenu.className = DROPDOWN_DEFAULT_CLASSNAMES; - } - - const { options = [] } = this.props; - const ops = options.map((option) => { - let value, displayText; - - if (typeof option === 'string') { - displayText = option; - value = option; - } else if (option !== null) { - displayText = option.displayText; - value = option.value; + /** Update the selected value when clicking the left/right buttons */ + const updateSelected = useCallback( + (direction: 'previous' | 'next') => { + if (options.length < 1 || disabled) { + return; } + const startIndex = 0; + const endIndex = options.length - 1; - return ( -
{ - this.setSelected(value); - }}> - {displayText} -
+ let selectedIndex = options.findIndex( + (option) => getOptionValue(option) === selected, ); - }); - const to_render = ops.length ? ops : 'No Options Found'; - - render(
{to_render}
, renderedMenu, () => { - let singletonPopper = Dropdown.singletonPopper; - if (singletonPopper === undefined) { - singletonPopper = createPopper(Dropdown.virtualElement, renderedMenu!, { - ...DEFAULT_OPTIONS, - placement: 'bottom-start', - }); - - Dropdown.singletonPopper = singletonPopper; - } else { - singletonPopper.setOptions({ - ...DEFAULT_OPTIONS, - placement: 'bottom-start', - }); - - singletonPopper.update(); + if (selectedIndex < 0) { + selectedIndex = direction === 'next' ? endIndex : startIndex; } - }); - } - setOpen(open: boolean) { - this.setState((state) => ({ - ...state, - open, - })); - if (open) { - setTimeout(() => { - this.openMenu(); - window.addEventListener('click', this.handleClick); - }); - } else { - this.closeMenu(); - window.removeEventListener('click', this.handleClick); - } - } + let newIndex = selectedIndex; + if (direction === 'next') { + newIndex = selectedIndex === endIndex ? startIndex : selectedIndex++; + } else { + newIndex = selectedIndex === startIndex ? endIndex : selectedIndex--; + } - setSelected(selected: string) { - this.setState((state) => ({ - ...state, - selected, - })); - this.setOpen(false); - if (this.props.onSelected) { - this.props.onSelected(selected); - } - } + onSelected?.(getOptionValue(options[newIndex])); + }, + [disabled, onSelected, options, selected], + ); - getOptionValue(option): string { - return typeof option === 'string' ? option : option.value; - } + /** Allows the menu to be scrollable on open */ + useEffect(() => { + if (!open) return; - getSelectedIndex(): number { - const selected = this.state.selected || this.props.selected; - const { options = [] } = this.props; + innerRef.current?.focus(); + }, [open]); - return options.findIndex((option) => { - return this.getOptionValue(option) === selected; - }); - } + return ( + setOpen(false)} + placement={over ? 'top-start' : 'bottom-start'} + content={ +
+ {options.length === 0 && ( +
No options
+ )} - toPrevious(): void { - if (this.props.options.length < 1) { - return; - } + {options.map((option, index) => { + const value = getOptionValue(option); - let selectedIndex = this.getSelectedIndex(); - const startIndex = 0; - const endIndex = this.props.options.length - 1; - - const hasSelected = selectedIndex >= 0; - if (!hasSelected) { - selectedIndex = startIndex; - } - - const previousIndex = - selectedIndex === startIndex ? endIndex : selectedIndex - 1; - - this.setSelected(this.getOptionValue(this.props.options[previousIndex])); - } - - toNext(): void { - if (this.props.options.length < 1) { - return; - } - - let selectedIndex = this.getSelectedIndex(); - const startIndex = 0; - const endIndex = this.props.options.length - 1; - - const hasSelected = selectedIndex >= 0; - if (!hasSelected) { - selectedIndex = endIndex; - } - - const nextIndex = - selectedIndex === endIndex ? startIndex : selectedIndex + 1; - - this.setSelected(this.getOptionValue(this.props.options[nextIndex])); - } - - render() { - const { props } = this; - const { - icon, - iconRotation, - iconSpin, - clipSelectedText = true, - color = 'default', - dropdownStyle, - over, - nochevron, - width, - onClick, - onSelected, - selected, - disabled, - displayText, - buttons, - ...boxProps - } = props; - const { className, ...rest } = boxProps; - - const adjustedOpen = over ? !this.state.open : this.state.open; - - return ( - - - { + setOpen(false); + onSelected?.(value); + }} + > + {typeof option === 'string' ? option : option.displayText} +
+ ); + })} +
+ } + > +
+
+
{ - if (disabled && !this.state.open) { + if (disabled && !open) { return; } - this.setOpen(!this.state.open); - if (onClick) { - onClick(event); - } + setOpen(!open); + onClick?.(event); }} - {...rest}> + > {icon && ( )} - {displayText || this.state.selected} + }} + > + {displayText || selected} - {nochevron || ( + {!noChevron && ( )} - - - {buttons && ( - <> - +
+ {buttons && ( + <>
+
+ + ); } diff --git a/tgui/packages/tgui/components/FakeTerminal.jsx b/tgui/packages/tgui/components/FakeTerminal.jsx index d6479a2579..f97f7e6bf3 100644 --- a/tgui/packages/tgui/components/FakeTerminal.jsx +++ b/tgui/packages/tgui/components/FakeTerminal.jsx @@ -1,5 +1,5 @@ import { Box } from './Box'; -import { Component, Fragment } from 'inferno'; +import { Component, Fragment } from 'react'; export class FakeTerminal extends Component { constructor(props) { diff --git a/tgui/packages/tgui/components/FitText.tsx b/tgui/packages/tgui/components/FitText.tsx index 334eb97073..049c887ed7 100644 --- a/tgui/packages/tgui/components/FitText.tsx +++ b/tgui/packages/tgui/components/FitText.tsx @@ -1,4 +1,10 @@ -import { Component, createRef, RefObject } from 'inferno'; +import { + Component, + createRef, + HTMLAttributes, + PropsWithChildren, + RefObject, +} from 'react'; const DEFAULT_ACCEPTABLE_DIFFERENCE = 5; @@ -7,7 +13,7 @@ type Props = { maxWidth: number; maxFontSize: number; native?: HTMLAttributes; -}; +} & PropsWithChildren; type State = { fontSize: number; @@ -19,8 +25,8 @@ export class FitText extends Component { fontSize: 0, }; - constructor() { - super(); + constructor(props: Props) { + super(props); this.resize = this.resize.bind(this); @@ -80,10 +86,12 @@ export class FitText extends Component { + fontSize: `${this.state.fontSize}px`, + ...(typeof this.props.native?.style === 'object' + ? this.props.native.style + : {}), + }} + > {this.props.children} ); diff --git a/tgui/packages/tgui/components/Flex.tsx b/tgui/packages/tgui/components/Flex.tsx index f67738280b..50dba27795 100644 --- a/tgui/packages/tgui/components/Flex.tsx +++ b/tgui/packages/tgui/components/Flex.tsx @@ -4,16 +4,20 @@ * @license MIT */ -import { BooleanLike, classes, pureComponentHooks } from 'common/react'; +import { classes } from 'common/react'; + import { BoxProps, computeBoxClassName, computeBoxProps, unit } from './Box'; -export type FlexProps = BoxProps & { - direction?: string | BooleanLike; - wrap?: string | BooleanLike; - align?: string | BooleanLike; - justify?: string | BooleanLike; - inline?: BooleanLike; -}; +export type FlexProps = Partial<{ + align: string | boolean; + direction: string; + inline: boolean; + justify: string; + scrollable: boolean; + style: Partial; + wrap: string | boolean; +}> & + BoxProps; export const computeFlexClassName = (props: FlexProps) => { return classes([ @@ -27,13 +31,14 @@ export const computeFlexClassName = (props: FlexProps) => { export const computeFlexProps = (props: FlexProps) => { const { className, direction, wrap, align, justify, inline, ...rest } = props; + return computeBoxProps({ style: { ...rest.style, - 'flex-direction': direction, - 'flex-wrap': wrap === true ? 'wrap' : wrap, - 'align-items': align, - 'justify-content': justify, + flexDirection: direction, + flexWrap: wrap === true ? 'wrap' : wrap, + alignItems: align, + justifyContent: justify, }, ...rest, }); @@ -49,15 +54,15 @@ export const Flex = (props) => { ); }; -Flex.defaultHooks = pureComponentHooks; - -export type FlexItemProps = BoxProps & { - grow?: number; - order?: number; - shrink?: number; - basis?: string | BooleanLike; - align?: string | BooleanLike; -}; +export type FlexItemProps = BoxProps & + Partial<{ + grow: number | boolean; + order: number; + shrink: number | boolean; + basis: string | number; + align: string | boolean; + style: Partial; + }>; export const computeFlexItemClassName = (props: FlexItemProps) => { return classes([ @@ -68,33 +73,26 @@ export const computeFlexItemClassName = (props: FlexItemProps) => { }; export const computeFlexItemProps = (props: FlexItemProps) => { - // prettier-ignore - const { - className, - style, - grow, - order, - shrink, - basis, - align, - ...rest - } = props; - // prettier-ignore - const computedBasis = basis + const { className, style, grow, order, shrink, basis, align, ...rest } = + props; + + const computedBasis = + basis ?? // IE11: Set basis to specified width if it's known, which fixes certain // bugs when rendering tables inside the flex. - ?? props.width + props.width ?? // If grow is used, basis should be set to 0 to be consistent with // flex css shorthand `flex: 1`. - ?? (grow !== undefined ? 0 : undefined); + (grow !== undefined ? 0 : undefined); + return computeBoxProps({ style: { ...style, - 'flex-grow': grow !== undefined && Number(grow), - 'flex-shrink': shrink !== undefined && Number(shrink), - 'flex-basis': unit(computedBasis), - 'order': order, - 'align-self': align, + flexGrow: grow !== undefined && Number(grow), + flexShrink: shrink !== undefined && Number(shrink), + flexBasis: unit(computedBasis), + order: order, + alignSelf: align, }, ...rest, }); @@ -110,6 +108,4 @@ const FlexItem = (props) => { ); }; -FlexItem.defaultHooks = pureComponentHooks; - Flex.Item = FlexItem; diff --git a/tgui/packages/tgui/components/Grid.jsx b/tgui/packages/tgui/components/Grid.jsx index 7ad3fd1dfd..f5593c9e00 100644 --- a/tgui/packages/tgui/components/Grid.jsx +++ b/tgui/packages/tgui/components/Grid.jsx @@ -5,7 +5,6 @@ */ import { Table } from './Table'; -import { pureComponentHooks } from 'common/react'; /** @deprecated */ export const Grid = (props) => { @@ -17,8 +16,6 @@ export const Grid = (props) => { ); }; -Grid.defaultHooks = pureComponentHooks; - /** @deprecated */ export const GridColumn = (props) => { const { size = 1, style, ...rest } = props; @@ -33,6 +30,4 @@ export const GridColumn = (props) => { ); }; -Grid.defaultHooks = pureComponentHooks; - Grid.Column = GridColumn; diff --git a/tgui/packages/tgui/components/Icon.tsx b/tgui/packages/tgui/components/Icon.tsx index da8b6af26a..79d3aa5f07 100644 --- a/tgui/packages/tgui/components/Icon.tsx +++ b/tgui/packages/tgui/components/Icon.tsx @@ -6,20 +6,20 @@ * @license MIT */ -import { classes, pureComponentHooks } from 'common/react'; -import { InfernoNode } from 'inferno'; +import { classes } from 'common/react'; +import { ReactNode } from 'react'; + import { BoxProps, computeBoxClassName, computeBoxProps } from './Box'; const FA_OUTLINE_REGEX = /-o$/; -type IconPropsUnique = { - name: string; - size?: number; - spin?: boolean; - className?: string; - rotation?: number; - style?: string | CSSProperties; -}; +type IconPropsUnique = { name: string } & Partial<{ + size: number; + spin: boolean; + className: string; + rotation: number; + style: Partial; +}>; export type IconProps = IconPropsUnique & BoxProps; @@ -31,7 +31,7 @@ export const Icon = (props: IconProps) => { if (!style) { style = {}; } - style['font-size'] = size * 100 + '%'; + style['fontSize'] = size * 100 + '%'; } if (rotation) { if (!style) { @@ -75,10 +75,8 @@ export const Icon = (props: IconProps) => { ); }; -Icon.defaultHooks = pureComponentHooks; - type IconStackUnique = { - children: InfernoNode; + children: ReactNode; className?: string; }; @@ -88,8 +86,9 @@ export const IconStack = (props: IconStackProps) => { const { className, children, ...rest } = props; return ( + className={classes(['IconStack', className, computeBoxClassName(rest)])} + {...computeBoxProps(rest)} + > {children} ); diff --git a/tgui/packages/tgui/components/InfinitePlane.jsx b/tgui/packages/tgui/components/InfinitePlane.jsx index 74f60f1e4d..3f2bf598c2 100644 --- a/tgui/packages/tgui/components/InfinitePlane.jsx +++ b/tgui/packages/tgui/components/InfinitePlane.jsx @@ -2,7 +2,7 @@ import { computeBoxProps } from './Box'; import { Stack } from './Stack'; import { ProgressBar } from './ProgressBar'; import { Button } from './Button'; -import { Component } from 'inferno'; +import { Component } from 'react'; const ZOOM_MIN_VAL = 0.5; const ZOOM_MAX_VAL = 1.5; @@ -10,8 +10,8 @@ const ZOOM_MAX_VAL = 1.5; const ZOOM_INCREMENT = 0.1; export class InfinitePlane extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.state = { mouseDown: false, @@ -139,30 +139,32 @@ export class InfinitePlane extends Component { overflow: 'hidden', position: 'relative', }, - })}> + })} + >
+ position: 'fixed', + transform: `translate(${finalLeft}px, ${finalTop}px) scale(${zoom})`, + transformOrigin: 'top left', + height: '100%', + width: '100%', + }} + > {children}
@@ -174,7 +176,8 @@ export class InfinitePlane extends Component { + maxValue={ZOOM_MAX_VAL} + > {zoom}x diff --git a/tgui/packages/tgui/components/Input.jsx b/tgui/packages/tgui/components/Input.jsx index ac7ce6eef3..a6d4c4ba8b 100644 --- a/tgui/packages/tgui/components/Input.jsx +++ b/tgui/packages/tgui/components/Input.jsx @@ -6,7 +6,7 @@ import { KEY_ENTER, KEY_ESCAPE } from 'common/keycodes'; import { classes } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { Box } from './Box'; // prettier-ignore @@ -17,8 +17,8 @@ export const toInputValue = value => ( ); export class Input extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.inputRef = createRef(); this.state = { editing: false, @@ -138,7 +138,8 @@ export class Input extends Component { monospace && 'Input--monospace', className, ])} - {...rest}> + {...rest} + >
.
{ dispose: () => void; - constructor() { - super(); + constructor(props) { + super(props); this.dispose = listenForKeyEvents((key) => { if (this.props.onKey) { diff --git a/tgui/packages/tgui/components/Knob.jsx b/tgui/packages/tgui/components/Knob.jsx index 1d40ff1ed2..5702c16129 100644 --- a/tgui/packages/tgui/components/Knob.jsx +++ b/tgui/packages/tgui/components/Knob.jsx @@ -8,14 +8,8 @@ import { keyOfMatchingRange, scale } from 'common/math'; import { classes } from 'common/react'; import { computeBoxClassName, computeBoxProps } from './Box'; import { DraggableControl } from './DraggableControl'; -import { NumberInput } from './NumberInput'; export const Knob = (props) => { - // IE8: I don't want to support a yet another component on IE8. - // IE8: It also can't handle SVG. - if (Byond.IS_LTE_IE8) { - return ; - } const { // Draggable props (passthrough) animated, @@ -57,7 +51,8 @@ export const Knob = (props) => { suppressFlicker, unit, value, - }}> + }} + > {(control) => { const { dragging, @@ -71,7 +66,7 @@ export const Knob = (props) => { const scaledFillValue = scale( fillValue ?? displayValue, minValue, - maxValue + maxValue, ); const scaledDisplayValue = scale(displayValue, minValue, maxValue); const effectiveColor = @@ -88,18 +83,20 @@ export const Knob = (props) => { ])} {...computeBoxProps({ style: { - 'font-size': size + 'em', + fontSize: size + 'em', ...style, }, ...rest, })} - onMouseDown={handleDragStart}> + onMouseDown={handleDragStart} + >
+ }} + >
@@ -108,20 +105,22 @@ export const Knob = (props) => { )} + viewBox="0 0 100 100" + > + viewBox="0 0 100 100" + > { wrap={wrap} align="stretch" justify="space-between" - {...rest}> + {...rest} + > {children} ); @@ -30,7 +31,8 @@ const LabeledControlsItem = (props) => { align="center" textAlign="center" justify="space-between" - {...rest}> + {...rest} + > {children} {label} diff --git a/tgui/packages/tgui/components/LabeledList.tsx b/tgui/packages/tgui/components/LabeledList.tsx index 283c0a2b91..2c2baa9c34 100644 --- a/tgui/packages/tgui/components/LabeledList.tsx +++ b/tgui/packages/tgui/components/LabeledList.tsx @@ -4,35 +4,33 @@ * @license MIT */ -import { BooleanLike, classes, pureComponentHooks } from 'common/react'; -import { InfernoNode } from 'inferno'; +import { BooleanLike, classes } from 'common/react'; +import { PropsWithChildren, ReactNode } from 'react'; + import { Box, unit } from './Box'; import { Divider } from './Divider'; +import { Tooltip } from './Tooltip'; -type LabeledListProps = { - children?: any; -}; - -export const LabeledList = (props: LabeledListProps) => { +export const LabeledList = (props: PropsWithChildren) => { const { children } = props; return {children}
; }; -LabeledList.defaultHooks = pureComponentHooks; - -type LabeledListItemProps = { - className?: string | BooleanLike; - label?: string | InfernoNode | BooleanLike; - labelColor?: string | BooleanLike; - labelWrap?: boolean; - color?: string | BooleanLike; - textAlign?: string | BooleanLike; - buttons?: InfernoNode; +type LabeledListItemProps = Partial<{ + buttons: ReactNode; + className: string | BooleanLike; + color: string; + key: string | number; + label: string | ReactNode | BooleanLike; + labelColor: string; + labelWrap: boolean; + textAlign: string; /** @deprecated */ - content?: any; - children?: InfernoNode; - verticalAlign?: string; -}; + content: any; + children: ReactNode; + verticalAlign: string; + tooltip: string; +}>; const LabeledListItem = (props: LabeledListItemProps) => { const { @@ -46,27 +44,56 @@ const LabeledListItem = (props: LabeledListItemProps) => { content, children, verticalAlign = 'baseline', + tooltip, } = props; + + let innerLabel; + if (label) { + innerLabel = label; + if (typeof label === 'string') innerLabel += ':'; + } + + if (tooltip !== undefined) { + innerLabel = ( + + + {innerLabel} + + + ); + } + + let labelChild = ( + + {innerLabel} + + ); + return ( - - {label ? (typeof label === 'string' ? label + ':' : label) : null} - + {labelChild} + verticalAlign={verticalAlign} + > {content} {children} @@ -77,8 +104,6 @@ const LabeledListItem = (props: LabeledListItemProps) => { ); }; -LabeledListItem.defaultHooks = pureComponentHooks; - type LabeledListDividerProps = { size?: number; }; @@ -90,16 +115,15 @@ const LabeledListDivider = (props: LabeledListDividerProps) => { + paddingTop: padding, + paddingBottom: padding, + }} + > ); }; -LabeledListDivider.defaultHooks = pureComponentHooks; - LabeledList.Item = LabeledListItem; LabeledList.Divider = LabeledListDivider; diff --git a/tgui/packages/tgui/components/MenuBar.tsx b/tgui/packages/tgui/components/MenuBar.tsx index 35de61ec9c..acf35e8281 100644 --- a/tgui/packages/tgui/components/MenuBar.tsx +++ b/tgui/packages/tgui/components/MenuBar.tsx @@ -5,9 +5,10 @@ */ import { classes } from 'common/react'; -import { Component, createRef, InfernoNode, RefObject } from 'inferno'; -import { Box } from './Box'; +import { Component, createRef, ReactNode, RefObject } from 'react'; + import { logger } from '../logging'; +import { Box } from './Box'; import { Icon } from './Icon'; type MenuProps = { @@ -53,7 +54,8 @@ class Menu extends Component { className={'MenuBar__menu'} style={{ width: width, - }}> + }} + > {children}
); @@ -105,15 +107,17 @@ class MenuBarButton extends Component { className, ])} {...rest} - onClick={disabled ? undefined : onClick} - onmouseover={onMouseOver}> + onClick={disabled ? () => null : onClick} + onMouseOver={onMouseOver} + > {display} {open && ( + onOutsideClick={onOutsideClick} + > {children} )} @@ -126,7 +130,7 @@ type MenuBarItemProps = { entry: string; children: any; openWidth: string; - display: InfernoNode; + display: ReactNode; setOpenMenuBar: (entry: string | null) => void; openMenuBar: string | null; setOpenOnHover: (flag: boolean) => void; @@ -169,7 +173,8 @@ export const Dropdown = (props: MenuBarItemProps) => { if (openOnHover) { setOpenMenuBar(entry); } - }}> + }} + > {children} ); @@ -185,7 +190,8 @@ const MenuItemToggle = (props) => { 'MenuBar__MenuItemToggle', 'MenuBar__hover', ])} - onClick={() => onClick(value)}> + onClick={() => onClick(value)} + >
{checked && }
@@ -205,7 +211,8 @@ const MenuItem = (props) => { 'MenuBar__MenuItem', 'MenuBar__hover', ])} - onClick={() => onClick(value)}> + onClick={() => onClick(value)} + > {displayText} ); diff --git a/tgui/packages/tgui/components/Modal.jsx b/tgui/packages/tgui/components/Modal.jsx index 54c8c090ec..9cad4de810 100644 --- a/tgui/packages/tgui/components/Modal.jsx +++ b/tgui/packages/tgui/components/Modal.jsx @@ -25,7 +25,8 @@ export const Modal = (props) => {
+ {...computeBoxProps(rest)} + > {children}
diff --git a/tgui/packages/tgui/components/NanoMap.jsx b/tgui/packages/tgui/components/NanoMap.jsx index 4ee5199119..d4f6d7a58c 100644 --- a/tgui/packages/tgui/components/NanoMap.jsx +++ b/tgui/packages/tgui/components/NanoMap.jsx @@ -1,4 +1,4 @@ -import { Component } from 'inferno'; +import { Component } from 'react'; import { Box, Button, Icon, Tooltip, LabeledList, Slider } from '.'; import { useBackend } from '../backend'; @@ -132,13 +132,13 @@ export class NanoMap extends Component { height: mapSize, 'margin-top': offsetY + 'px', 'margin-left': offsetX + 'px', - 'overflow': 'hidden', - 'position': 'relative', + overflow: 'hidden', + position: 'relative', 'background-image': 'url(' + mapUrl + ')', 'background-size': 'cover', 'background-repeat': 'no-repeat', 'text-align': 'center', - 'cursor': dragging ? 'move' : 'auto', + cursor: dragging ? 'move' : 'auto', }; return ( @@ -147,7 +147,8 @@ export class NanoMap extends Component { style={newStyle} textAlign="center" onMouseDown={this.handleDragStart} - onClick={this.handleOnClick}> + onClick={this.handleOnClick} + > {children} @@ -176,7 +177,8 @@ const NanoMapMarker = (props, context) => { lineHeight="0" bottom={ry + 'px'} left={rx + 'px'} - onMouseDown={handleOnClick}> + onMouseDown={handleOnClick} + > @@ -186,8 +188,8 @@ const NanoMapMarker = (props, context) => { NanoMap.Marker = NanoMapMarker; -const NanoMapZoomer = (props, context) => { - const { act, config, data } = useBackend(context); +const NanoMapZoomer = (props) => { + const { act, config, data } = useBackend(); return ( @@ -210,7 +212,7 @@ const NanoMapZoomer = (props, context) => { selected={~~level === ~~config.mapZLevel} content={level} onClick={() => { - act('setZLevel', { 'mapZLevel': level }); + act('setZLevel', { mapZLevel: level }); }} /> ))} diff --git a/tgui/packages/tgui/components/NoticeBox.jsx b/tgui/packages/tgui/components/NoticeBox.jsx index 1c3b49b16a..09e205b46b 100644 --- a/tgui/packages/tgui/components/NoticeBox.jsx +++ b/tgui/packages/tgui/components/NoticeBox.jsx @@ -4,7 +4,7 @@ * @license MIT */ -import { classes, pureComponentHooks } from 'common/react'; +import { classes } from 'common/react'; import { Box } from './Box'; export const NoticeBox = (props) => { @@ -23,5 +23,3 @@ export const NoticeBox = (props) => { /> ); }; - -NoticeBox.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui/components/NumberInput.jsx b/tgui/packages/tgui/components/NumberInput.jsx index e264d811d3..24c7a7d7ac 100644 --- a/tgui/packages/tgui/components/NumberInput.jsx +++ b/tgui/packages/tgui/components/NumberInput.jsx @@ -5,8 +5,8 @@ */ import { clamp } from 'common/math'; -import { classes, pureComponentHooks } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { classes } from 'common/react'; +import { Component, createRef } from 'react'; import { AnimatedNumber } from './AnimatedNumber'; import { Box } from './Box'; @@ -40,7 +40,7 @@ export class NumberInput extends Component { this.setState({ suppressingFlicker: false, }), - suppressFlicker + suppressFlicker, ); } }; @@ -87,13 +87,13 @@ export class NumberInput extends Component { state.internalValue = clamp( state.internalValue + (offset * step) / stepPixelSize, minValue - step, - maxValue + step + maxValue + step, ); // Clamp the final value state.value = clamp( state.internalValue - (state.internalValue % step) + stepOffset, minValue, - maxValue + maxValue, ); state.origin = e.screenY; } else if (Math.abs(offset) > 4) { @@ -165,14 +165,15 @@ export class NumberInput extends Component { displayValue = intermediateValue; } - // prettier-ignore const contentElement = ( -
- { - (animated && !dragging && !suppressingFlicker) ? - () : - (format ? format(displayValue) : displayValue) - } +
+ {animated && !dragging && !suppressingFlicker ? ( + + ) : format ? ( + format(displayValue) + ) : ( + displayValue + )} {unit ? ' ' + unit : ''}
@@ -189,15 +190,18 @@ export class NumberInput extends Component { minHeight={height} lineHeight={lineHeight} fontSize={fontSize} - onMouseDown={this.handleDragStart}> + onMouseDown={this.handleDragStart} + >
@@ -208,8 +212,8 @@ export class NumberInput extends Component { style={{ display: !editing ? 'none' : undefined, height: height, - 'line-height': lineHeight, - 'font-size': fontSize, + lineHeight: lineHeight, + fontSize: fontSize, }} onBlur={(e) => { if (!editing) { @@ -274,7 +278,6 @@ export class NumberInput extends Component { } } -NumberInput.defaultHooks = pureComponentHooks; NumberInput.defaultProps = { minValue: -Infinity, maxValue: +Infinity, diff --git a/tgui/packages/tgui/components/Popper.tsx b/tgui/packages/tgui/components/Popper.tsx index 907896d6b8..dbc7c18f7b 100644 --- a/tgui/packages/tgui/components/Popper.tsx +++ b/tgui/packages/tgui/components/Popper.tsx @@ -1,79 +1,96 @@ -import { createPopper } from '@popperjs/core'; -import { ArgumentsOf } from 'common/types'; -import { Component, findDOMfromVNode, InfernoNode, render } from 'inferno'; +import { Placement } from '@popperjs/core'; +import { + PropsWithChildren, + ReactNode, + useEffect, + useRef, + useState, +} from 'react'; +import { usePopper } from 'react-popper'; -type PopperProps = { - popperContent: InfernoNode; - options?: ArgumentsOf[2]; - additionalStyles?: CSSProperties; +type RequiredProps = { + /** The content to display in the popper */ + content: ReactNode; + /** Whether the popper is open */ + isOpen: boolean; }; -export class Popper extends Component { - static id: number = 0; +type OptionalProps = Partial<{ + /** Called when the user clicks outside the popper */ + onClickOutside: () => void; + /** Where to place the popper relative to the reference element */ + placement: Placement; +}>; - renderedContent: HTMLDivElement; - popperInstance: ReturnType; +type Props = RequiredProps & OptionalProps; - constructor() { - super(); +/** + * ## Popper + * Popper lets you position elements so that they don't go out of the bounds of the window. + * @url https://popper.js.org/react-popper/ for more information. + */ +export function Popper(props: PropsWithChildren) { + const { children, content, isOpen, onClickOutside, placement } = props; - Popper.id += 1; + const [referenceElement, setReferenceElement] = + useState(null); + const [popperElement, setPopperElement] = useState( + null, + ); + + // One would imagine we could just use useref here, but it's against react-popper documentation and causes a positioning bug + // We still need them to call focus and clickoutside events :( + const popperRef = useRef(null); + const parentRef = useRef(null); + + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement, + }); + + /** Close the popper when the user clicks outside */ + function handleClickOutside(event: MouseEvent) { + if ( + !popperRef.current?.contains(event.target as Node) && + !parentRef.current?.contains(event.target as Node) + ) { + onClickOutside?.(); + } } - componentDidMount() { - const { additionalStyles, options } = this.props; - - this.renderedContent = document.createElement('div'); - - if (additionalStyles) { - for (const [attribute, value] of Object.entries(additionalStyles)) { - this.renderedContent.style[attribute] = value; - } + useEffect(() => { + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + } else { + document.removeEventListener('mousedown', handleClickOutside); } - this.renderPopperContent(() => { - document.body.appendChild(this.renderedContent); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isOpen]); - // HACK: We don't want to create a wrapper, as it could break the layout - // of consumers, so we do the inferno equivalent of `findDOMNode(this)`. - // This is usually bad as refs are usually better, but refs did - // not work in this case, as they weren't propagating correctly. - // A previous attempt was made as a render prop that passed an ID, - // but this made consuming use too unwieldly. - // This code is copied from `findDOMNode` in inferno-extras. - // Because this component is written in TypeScript, we will know - // immediately if this internal variable is removed. - const domNode = findDOMfromVNode(this.$LI, true); - if (!domNode) { - return; - } - - this.popperInstance = createPopper( - domNode, - this.renderedContent, - options - ); - }); - } - - componentDidUpdate() { - this.renderPopperContent(() => this.popperInstance?.update()); - } - - componentWillUnmount() { - this.popperInstance?.destroy(); - render(null, this.renderedContent, () => { - this.renderedContent.remove(); - }); - } - - renderPopperContent(callback: () => void) { - // `render` errors when given false, so we convert it to `null`, - // which is supported. - render(this.props.popperContent || null, this.renderedContent, callback); - } - - render() { - return this.props.children; - } + return ( + <> +
{ + setReferenceElement(node); + parentRef.current = node; + }} + > + {children} +
+ {isOpen && ( +
{ + setPopperElement(node); + popperRef.current = node; + }} + style={{ ...styles.popper, zIndex: 5 }} + {...attributes.popper} + > + {content} +
+ )} + + ); } diff --git a/tgui/packages/tgui/components/ProgressBar.jsx b/tgui/packages/tgui/components/ProgressBar.tsx similarity index 54% rename from tgui/packages/tgui/components/ProgressBar.jsx rename to tgui/packages/tgui/components/ProgressBar.tsx index 86116732c3..9cb442479e 100644 --- a/tgui/packages/tgui/components/ProgressBar.jsx +++ b/tgui/packages/tgui/components/ProgressBar.tsx @@ -4,12 +4,31 @@ * @license MIT */ -import { clamp01, scale, keyOfMatchingRange, toFixed } from 'common/math'; -import { classes, pureComponentHooks } from 'common/react'; -import { computeBoxClassName, computeBoxProps } from './Box'; -import { CSS_COLORS } from '../constants'; +import { clamp01, keyOfMatchingRange, scale, toFixed } from 'common/math'; +import { classes } from 'common/react'; +import { PropsWithChildren } from 'react'; -export const ProgressBar = (props) => { +import { CSS_COLORS } from '../constants'; +import { BoxProps, computeBoxClassName, computeBoxProps } from './Box'; + +type Props = { + value: number; +} & Partial<{ + backgroundColor: string; + className: string; + color: string; + height: string | number; + maxValue: number; + minValue: number; + ranges: Record; + style: Partial; + title: string; + width: string | number; +}> & + Partial & + PropsWithChildren; + +export const ProgressBar = (props: Props) => { const { className, value, @@ -22,32 +41,25 @@ export const ProgressBar = (props) => { } = props; const scaledValue = scale(value, minValue, maxValue); const hasContent = children !== undefined; - // prettier-ignore - const effectiveColor = color - || keyOfMatchingRange(value, ranges) - || 'default'; + + const effectiveColor = + color || keyOfMatchingRange(value, ranges) || 'default'; // We permit colors to be in hex format, rgb()/rgba() format, // a name for a color- class, or a base CSS class. const outerProps = computeBoxProps(rest); - // prettier-ignore - const outerClasses = [ - 'ProgressBar', - className, - computeBoxClassName(rest), - ]; + + const outerClasses = ['ProgressBar', className, computeBoxClassName(rest)]; const fillStyles = { - 'width': clamp01(scaledValue) * 100 + '%', + width: clamp01(scaledValue) * 100 + '%', }; if (CSS_COLORS.includes(effectiveColor) || effectiveColor === 'default') { // If the color is a color- class, just use that. outerClasses.push('ProgressBar--color--' + effectiveColor); } else { // Otherwise, set styles directly. - // prettier-ignore - outerProps.style = (outerProps.style || "") - + `border-color: ${effectiveColor};`; - fillStyles['background-color'] = effectiveColor; + outerProps.style = { ...outerProps.style, borderColor: effectiveColor }; + fillStyles['backgroundColor'] = effectiveColor; } return ( @@ -62,5 +74,3 @@ export const ProgressBar = (props) => {
); }; - -ProgressBar.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui/components/RestrictedInput.jsx b/tgui/packages/tgui/components/RestrictedInput.jsx index 1b082296ca..362510fbfa 100644 --- a/tgui/packages/tgui/components/RestrictedInput.jsx +++ b/tgui/packages/tgui/components/RestrictedInput.jsx @@ -1,6 +1,6 @@ import { classes } from 'common/react'; import { clamp } from 'common/math'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { Box } from './Box'; import { KEY_ESCAPE, KEY_ENTER } from 'common/keycodes'; @@ -29,8 +29,8 @@ const getClampedNumber = (value, minValue, maxValue, allowFloats) => { }; export class RestrictedInput extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.inputRef = createRef(); this.state = { editing: false, @@ -47,7 +47,7 @@ export class RestrictedInput extends Component { e.target.value, minValue, maxValue, - allowFloats + allowFloats, ); if (onChange) { onChange(e, +e.target.value); @@ -76,7 +76,7 @@ export class RestrictedInput extends Component { e.target.value, minValue, maxValue, - allowFloats + allowFloats, ); this.setEditing(false); if (onChange) { @@ -110,7 +110,7 @@ export class RestrictedInput extends Component { nextValue, minValue, maxValue, - allowFloats + allowFloats, ); } if (this.props.autoFocus || this.props.autoSelect) { @@ -136,7 +136,7 @@ export class RestrictedInput extends Component { nextValue, minValue, maxValue, - allowFloats + allowFloats, ); } } @@ -158,7 +158,8 @@ export class RestrictedInput extends Component { monospace && 'Input--monospace', className, ])} - {...rest}> + {...rest} + >
.
{ - // Support for IE8 is for losers sorry B) - if (Byond.IS_LTE_IE8) { - return ; - } - const { value, minValue = 1, @@ -31,7 +26,7 @@ export const RoundGauge = (props) => { const scaledValue = scale(value, minValue, maxValue); const clampedValue = clamp01(scaledValue); - const scaledRanges = ranges ? {} : { 'primary': [0, 1] }; + const scaledRanges = ranges ? {} : { primary: [0, 1] }; if (ranges) { Object.keys(ranges).forEach((x) => { const range = ranges[x]; @@ -73,18 +68,20 @@ export const RoundGauge = (props) => { ])} {...computeBoxProps({ style: { - 'font-size': size + 'em', + fontSize: size + 'em', ...style, }, ...rest, - })}> + })} + > {(alertAfter || alertBefore) && ( + ])} + > )} @@ -99,9 +96,9 @@ export const RoundGauge = (props) => { className={`RoundGauge__ringFill RoundGauge--color--${x}`} key={i} style={{ - 'stroke-dashoffset': Math.max( + strokeDashoffset: Math.max( (2.0 - (col_ranges[1] - col_ranges[0])) * Math.PI * 50, - 0 + 0, ), }} transform={`rotate(${180 + 180 * col_ranges[0]} 50 50)`} @@ -114,7 +111,8 @@ export const RoundGauge = (props) => { + transform={`rotate(${clampedValue * 180 - 90} 50 50)`} + > ; - buttons?: InfernoNode; - fill?: boolean; - fitted?: boolean; - scrollable?: boolean; - scrollableHorizontal?: boolean; +export type SectionProps = Partial<{ + buttons: ReactNode; + fill: boolean; + fitted: boolean; + scrollable: boolean; + scrollableHorizontal: boolean; + title: ReactNode; + /** @member Allows external control of scrolling. */ + scrollableRef: RefObject; + /** @member Callback function for the `scroll` event */ + onScroll: ((this: GlobalEventHandlers, ev: Event) => any) | null; + flexGrow?: boolean; // VOREStation Addition noTopPadding?: boolean; // VOREStation Addition stretchContents?: boolean; // VOREStation Addition - /** @deprecated This property no longer works, please remove it. */ - level?: never; - /** @deprecated Please use `scrollable` property */ - overflowY?: never; - /** @member Allows external control of scrolling. */ - scrollableRef?: RefObject; - /** @member Callback function for the `scroll` event */ - onScroll?: (this: GlobalEventHandlers, ev: Event) => any; -} +}> & + BoxProps; -export class Section extends Component { - scrollableRef: RefObject; - scrollable: boolean; - onScroll?: (this: GlobalEventHandlers, ev: Event) => any; - scrollableHorizontal: boolean; +export const Section = (props: SectionProps) => { + const { + className, + title, + buttons, + fill, + fitted, + scrollable, + scrollableHorizontal, + flexGrow, // VOREStation Addition + noTopPadding, // VOREStation Addition + stretchContents, // VOREStation Addition + children, + onScroll, + ...rest + } = props; - constructor(props) { - super(props); - this.scrollableRef = props.scrollableRef || createRef(); - this.scrollable = props.scrollable; - this.onScroll = props.onScroll; - this.scrollableHorizontal = props.scrollableHorizontal; - } + const scrollableRef = props.scrollableRef || createRef(); + const hasTitle = canRender(title) || canRender(buttons); - componentDidMount() { - if (this.scrollable || this.scrollableHorizontal) { - addScrollableNode(this.scrollableRef.current as HTMLElement); - if (this.onScroll && this.scrollableRef.current) { - this.scrollableRef.current.onscroll = this.onScroll; + useEffect(() => { + if (scrollable || scrollableHorizontal) { + addScrollableNode(scrollableRef.current as HTMLElement); + if (onScroll && scrollableRef.current) { + scrollableRef.current.onscroll = onScroll; } } - } - componentWillUnmount() { - if (this.scrollable || this.scrollableHorizontal) { - removeScrollableNode(this.scrollableRef.current as HTMLElement); - } - } + return () => { + if (scrollable || scrollableHorizontal) { + removeScrollableNode(scrollableRef.current as HTMLElement); + } + }; + }, []); - render() { - const { - className, - title, - buttons, - fill, - fitted, - scrollable, - scrollableHorizontal, - flexGrow, // VOREStation Addition - noTopPadding, // VOREStation Addition - stretchContents, // VOREStation Addition - children, - onScroll, - ...rest - } = this.props; - const hasTitle = canRender(title) || canRender(buttons); - return ( -
- {hasTitle && ( -
- {title} -
{buttons}
-
- )} -
- {/* Vorestation Edit Start */} -
- {children} -
- {/* Vorestation Edit End */} + return ( +
+ {hasTitle && ( +
+ {title} +
{buttons}
+
+ )} +
+
+ {children}
- ); - } -} +
+ ); +}; diff --git a/tgui/packages/tgui/components/Slider.jsx b/tgui/packages/tgui/components/Slider.jsx index 84ee8421a9..aa84a41728 100644 --- a/tgui/packages/tgui/components/Slider.jsx +++ b/tgui/packages/tgui/components/Slider.jsx @@ -8,13 +8,8 @@ import { clamp01, keyOfMatchingRange, scale } from 'common/math'; import { classes } from 'common/react'; import { computeBoxClassName, computeBoxProps } from './Box'; import { DraggableControl } from './DraggableControl'; -import { NumberInput } from './NumberInput'; export const Slider = (props) => { - // IE8: I don't want to support a yet another component on IE8. - if (Byond.IS_LTE_IE8) { - return ; - } const { // Draggable props (passthrough) animated, @@ -52,7 +47,8 @@ export const Slider = (props) => { suppressFlicker, unit, value, - }}> + }} + > {(control) => { const { dragging, @@ -68,7 +64,7 @@ export const Slider = (props) => { const scaledFillValue = scale( fillValue ?? displayValue, minValue, - maxValue + maxValue, ); const scaledDisplayValue = scale(displayValue, minValue, maxValue); // prettier-ignore @@ -84,7 +80,8 @@ export const Slider = (props) => { computeBoxClassName(rest), ])} {...computeBoxProps(rest)} - onMouseDown={handleDragStart}> + onMouseDown={handleDragStart} + >
{ className="Slider__cursorOffset" style={{ width: clamp01(scaledDisplayValue) * 100 + '%', - }}> + }} + >
{dragging && ( diff --git a/tgui/packages/tgui/components/Stack.tsx b/tgui/packages/tgui/components/Stack.tsx index 9abf13e651..2564d20a18 100644 --- a/tgui/packages/tgui/components/Stack.tsx +++ b/tgui/packages/tgui/components/Stack.tsx @@ -5,15 +5,25 @@ */ import { classes } from 'common/react'; -import { RefObject } from 'inferno'; -import { computeFlexClassName, computeFlexItemClassName, computeFlexItemProps, computeFlexProps, FlexItemProps, FlexProps } from './Flex'; +import { RefObject } from 'react'; -type StackProps = FlexProps & { - vertical?: boolean; - fill?: boolean; -}; +import { + computeFlexClassName, + computeFlexItemClassName, + computeFlexItemProps, + computeFlexProps, + FlexItemProps, + FlexProps, +} from './Flex'; -export const Stack = (props: StackProps) => { +type Props = Partial<{ + vertical: boolean; + fill: boolean; + zebra: boolean; +}> & + FlexProps; + +export const Stack = (props: Props) => { const { className, vertical, fill, ...rest } = props; return (
{ ); }; -type StackItemProps = FlexProps & { - innerRef?: RefObject; -}; +type StackItemProps = FlexItemProps & + Partial<{ + innerRef: RefObject; + }>; const StackItem = (props: StackItemProps) => { const { className, innerRef, ...rest } = props; @@ -53,9 +64,10 @@ const StackItem = (props: StackItemProps) => { Stack.Item = StackItem; -type StackDividerProps = FlexItemProps & { - hidden?: boolean; -}; +type StackDividerProps = FlexItemProps & + Partial<{ + hidden: boolean; + }>; const StackDivider = (props: StackDividerProps) => { const { className, hidden, ...rest } = props; diff --git a/tgui/packages/tgui/components/StyleableSection.tsx b/tgui/packages/tgui/components/StyleableSection.tsx index ad38984fee..53b193707f 100644 --- a/tgui/packages/tgui/components/StyleableSection.tsx +++ b/tgui/packages/tgui/components/StyleableSection.tsx @@ -1,25 +1,29 @@ -import { SFC } from 'inferno'; +import { PropsWithChildren, ReactNode } from 'react'; + import { Box } from './Box'; +type Props = Partial<{ + style: Record; + titleStyle: Record; + textStyle: Record; + title: ReactNode; + titleSubtext: string; +}> & + PropsWithChildren; + // The cost of flexibility and prettiness. -export const StyleableSection: SFC<{ - style?; - titleStyle?; - textStyle?; - title?; - titleSubtext?; -}> = (props) => { +export const StyleableSection = (props: Props) => { return ( {/* Yes, this box (line above) is missing the "Section" class. This is very intentional, as the layout looks *ugly* with it.*/} - - + + {props.title}
{props.titleSubtext}
- - {props.children} + + {props.children} ); diff --git a/tgui/packages/tgui/components/Table.jsx b/tgui/packages/tgui/components/Table.jsx index e936b91448..f9a59b36c7 100644 --- a/tgui/packages/tgui/components/Table.jsx +++ b/tgui/packages/tgui/components/Table.jsx @@ -4,7 +4,7 @@ * @license MIT */ -import { classes, pureComponentHooks } from 'common/react'; +import { classes } from 'common/react'; import { computeBoxClassName, computeBoxProps } from './Box'; export const Table = (props) => { @@ -17,14 +17,13 @@ export const Table = (props) => { className, computeBoxClassName(rest), ])} - {...computeBoxProps(rest)}> + {...computeBoxProps(rest)} + > {children} ); }; -Table.defaultHooks = pureComponentHooks; - export const TableRow = (props) => { const { className, header, ...rest } = props; return ( @@ -40,8 +39,6 @@ export const TableRow = (props) => { ); }; -TableRow.defaultHooks = pureComponentHooks; - export const TableCell = (props) => { const { className, collapsing, header, ...rest } = props; return ( @@ -58,7 +55,5 @@ export const TableCell = (props) => { ); }; -TableCell.defaultHooks = pureComponentHooks; - Table.Row = TableRow; Table.Cell = TableCell; diff --git a/tgui/packages/tgui/components/Tabs.jsx b/tgui/packages/tgui/components/Tabs.tsx similarity index 63% rename from tgui/packages/tgui/components/Tabs.jsx rename to tgui/packages/tgui/components/Tabs.tsx index 46d1b078a8..84779bcf74 100644 --- a/tgui/packages/tgui/components/Tabs.jsx +++ b/tgui/packages/tgui/components/Tabs.tsx @@ -5,11 +5,35 @@ */ import { canRender, classes } from 'common/react'; -import { computeBoxClassName, computeBoxProps } from './Box'; +import { PropsWithChildren, ReactNode } from 'react'; + +import { BoxProps, computeBoxClassName, computeBoxProps } from './Box'; import { Icon } from './Icon'; -export const Tabs = (props) => { +type Props = Partial<{ + className: string; + fill: boolean; + fluid: boolean; + vertical: boolean; +}> & + BoxProps & + PropsWithChildren; + +type TabProps = Partial<{ + className: string; + color: string; + icon: string; + leftSlot: ReactNode; + onClick: (e?) => void; + rightSlot: ReactNode; + selected: boolean; +}> & + BoxProps & + PropsWithChildren; + +export const Tabs = (props: Props) => { const { className, vertical, fill, fluid, children, ...rest } = props; + return (
{ className, computeBoxClassName(rest), ])} - {...computeBoxProps(rest)}> + {...computeBoxProps(rest)} + > {children}
); }; -const Tab = (props) => { +const Tab = (props: TabProps) => { const { className, selected, @@ -37,6 +62,7 @@ const Tab = (props) => { children, ...rest } = props; + return (
{ 'Tab--color--' + color, selected && 'Tab--selected', className, - ...computeBoxClassName(rest), + computeBoxClassName(rest), ])} - {...computeBoxProps(rest)}> + {...computeBoxProps(rest)} + > {(canRender(leftSlot) &&
{leftSlot}
) || (!!icon && (
diff --git a/tgui/packages/tgui/components/TextArea.jsx b/tgui/packages/tgui/components/TextArea.jsx index 4fdf554242..560915ffac 100644 --- a/tgui/packages/tgui/components/TextArea.jsx +++ b/tgui/packages/tgui/components/TextArea.jsx @@ -6,7 +6,7 @@ */ import { classes } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { Box } from './Box'; import { toInputValue } from './Input'; import { KEY_ENTER, KEY_ESCAPE, KEY_TAB } from 'common/keycodes'; @@ -185,6 +185,7 @@ export class TextArea extends Component { displayedValue, ...boxProps } = this.props; + // Box props const { className, fluid, nowrap, ...rest } = boxProps; const { scrolledAmount } = this.state; @@ -196,7 +197,8 @@ export class TextArea extends Component { noborder && 'TextArea--noborder', className, ])} - {...rest}> + {...rest} + > {!!displayedValue && (
+ transform: `translateY(-${scrolledAmount}px)`, + }} + > {displayedValue}
@@ -228,7 +231,7 @@ export class TextArea extends Component { onScroll={this.handleScroll} maxLength={maxLength} style={{ - 'color': displayedValue ? 'rgba(0, 0, 0, 0)' : 'inherit', + color: displayedValue ? 'rgba(0, 0, 0, 0)' : 'inherit', }} /> diff --git a/tgui/packages/tgui/components/TimeDisplay.jsx b/tgui/packages/tgui/components/TimeDisplay.jsx index 6b87ee5260..bbdd747701 100644 --- a/tgui/packages/tgui/components/TimeDisplay.jsx +++ b/tgui/packages/tgui/components/TimeDisplay.jsx @@ -1,5 +1,5 @@ import { formatTime } from '../format'; -import { Component } from 'inferno'; +import { Component } from 'react'; // AnimatedNumber Copypaste const isSafeNumber = (value) => { diff --git a/tgui/packages/tgui/components/Tooltip.tsx b/tgui/packages/tgui/components/Tooltip.tsx index cb35425e74..f3936f32c6 100644 --- a/tgui/packages/tgui/components/Tooltip.tsx +++ b/tgui/packages/tgui/components/Tooltip.tsx @@ -1,9 +1,12 @@ +/* eslint-disable react/no-deprecated */ +// TODO: Rewrite as an FC, remove this lint disable import { createPopper, Placement, VirtualElement } from '@popperjs/core'; -import { Component, findDOMfromVNode, InfernoNode, render } from 'inferno'; +import { Component, ReactNode } from 'react'; +import { findDOMNode, render } from 'react-dom'; type TooltipProps = { - children?: InfernoNode; - content: InfernoNode; + children?: ReactNode; + content: ReactNode; position?: Placement; }; @@ -50,14 +53,16 @@ export class Tooltip extends Component { getDOMNode() { // HACK: We don't want to create a wrapper, as it could break the layout - // of consumers, so we do the inferno equivalent of `findDOMNode(this)`. - // My attempt to avoid this was a render prop that passed in - // callbacks to onmouseenter and onmouseleave, but this was unwiedly - // to consumers, specifically buttons. - // This code is copied from `findDOMNode` in inferno-extras. + // of consumers, so we use findDOMNode. + // This is usually bad as refs are usually better, but refs did + // not work in this case, as they weren't propagating correctly. + // A previous attempt was made as a render prop that passed an ID, + // but this made consuming use too unwieldly. // Because this component is written in TypeScript, we will know // immediately if this internal variable is removed. - return findDOMfromVNode(this.$LI, true); + // + // eslint-disable-next-line react/no-find-dom-node + return findDOMNode(this) as Element; } componentDidMount() { @@ -103,33 +108,28 @@ export class Tooltip extends Component { return; } - render( - {this.props.content}, - renderedTooltip, - () => { - let singletonPopper = Tooltip.singletonPopper; - if (singletonPopper === undefined) { - singletonPopper = createPopper( - Tooltip.virtualElement, - renderedTooltip!, - { - ...DEFAULT_OPTIONS, - placement: this.props.position || 'auto', - } - ); - - Tooltip.singletonPopper = singletonPopper; - } else { - singletonPopper.setOptions({ + render({this.props.content}, renderedTooltip, () => { + let singletonPopper = Tooltip.singletonPopper; + if (singletonPopper === undefined) { + singletonPopper = createPopper( + Tooltip.virtualElement, + renderedTooltip!, + { ...DEFAULT_OPTIONS, placement: this.props.position || 'auto', - }); + }, + ); - singletonPopper.update(); - } - }, - this.context - ); + Tooltip.singletonPopper = singletonPopper; + } else { + singletonPopper.setOptions({ + ...DEFAULT_OPTIONS, + placement: this.props.position || 'auto', + }); + + singletonPopper.update(); + } + }); } componentDidUpdate() { diff --git a/tgui/packages/tgui/components/TrackOutsideClicks.tsx b/tgui/packages/tgui/components/TrackOutsideClicks.tsx index 578b4f55db..c8451fe12d 100644 --- a/tgui/packages/tgui/components/TrackOutsideClicks.tsx +++ b/tgui/packages/tgui/components/TrackOutsideClicks.tsx @@ -1,14 +1,14 @@ -import { Component, createRef } from 'inferno'; +import { Component, createRef, PropsWithChildren } from 'react'; type Props = { onOutsideClick: () => void; -}; +} & PropsWithChildren; export class TrackOutsideClicks extends Component { ref = createRef(); - constructor() { - super(); + constructor(props) { + super(props); this.handleOutsideClick = this.handleOutsideClick.bind(this); diff --git a/tgui/packages/tgui/components/index.ts b/tgui/packages/tgui/components/index.ts index 6e117bf838..a0e8d885d8 100644 --- a/tgui/packages/tgui/components/index.ts +++ b/tgui/packages/tgui/components/index.ts @@ -14,12 +14,13 @@ export { ByondUi } from './ByondUi'; export { Chart } from './Chart'; export { Collapsible } from './Collapsible'; export { ColorBox } from './ColorBox'; +export { Dialog } from './Dialog'; export { Dimmer } from './Dimmer'; export { Divider } from './Divider'; export { DraggableControl } from './DraggableControl'; export { Dropdown } from './Dropdown'; -export { Flex } from './Flex'; export { FitText } from './FitText'; +export { Flex } from './Flex'; export { Grid } from './Grid'; export { Icon } from './Icon'; export { InfinitePlane } from './InfinitePlane'; @@ -33,18 +34,17 @@ export { Modal } from './Modal'; export { NanoMap } from './NanoMap'; export { NoticeBox } from './NoticeBox'; export { NumberInput } from './NumberInput'; -export { ProgressBar } from './ProgressBar'; export { Popper } from './Popper'; +export { ProgressBar } from './ProgressBar'; export { RestrictedInput } from './RestrictedInput'; export { RoundGauge } from './RoundGauge'; export { Section } from './Section'; export { Slider } from './Slider'; -export { StyleableSection } from './StyleableSection'; export { Stack } from './Stack'; +export { StyleableSection } from './StyleableSection'; export { Table } from './Table'; export { Tabs } from './Tabs'; export { TextArea } from './TextArea'; export { TimeDisplay } from './TimeDisplay'; -export { TrackOutsideClicks } from './TrackOutsideClicks'; export { Tooltip } from './Tooltip'; -export { Dialog } from './Dialog'; +export { TrackOutsideClicks } from './TrackOutsideClicks'; diff --git a/tgui/packages/tgui/constants.ts b/tgui/packages/tgui/constants.ts index f4cb574dbb..909f804179 100644 --- a/tgui/packages/tgui/constants.ts +++ b/tgui/packages/tgui/constants.ts @@ -91,94 +91,94 @@ export const CSS_COLORS = [ // go use /client/verb/generate_tgui_radio_constants() in communications.dm. export const RADIO_CHANNELS = [ { - 'name': 'Mercenary', - 'freq': 1213, - 'color': '#6D3F40', + name: 'Mercenary', + freq: 1213, + color: '#6D3F40', }, { - 'name': 'Raider', - 'freq': 1277, - 'color': '#6D3F40', + name: 'Raider', + freq: 1277, + color: '#6D3F40', }, { - 'name': 'Special Ops', - 'freq': 1341, - 'color': '#5C5C8A', + name: 'Special Ops', + freq: 1341, + color: '#5C5C8A', }, { - 'name': 'AI Private', - 'freq': 1343, - 'color': '#FF00FF', + name: 'AI Private', + freq: 1343, + color: '#FF00FF', }, { - 'name': 'Response Team', - 'freq': 1345, - 'color': '#5C5C8A', + name: 'Response Team', + freq: 1345, + color: '#5C5C8A', }, { - 'name': 'Supply', - 'freq': 1347, - 'color': '#5F4519', + name: 'Supply', + freq: 1347, + color: '#5F4519', }, { - 'name': 'Service', - 'freq': 1349, - 'color': '#6eaa2c', + name: 'Service', + freq: 1349, + color: '#6eaa2c', }, { - 'name': 'Science', - 'freq': 1351, - 'color': '#993399', + name: 'Science', + freq: 1351, + color: '#993399', }, { - 'name': 'Command', - 'freq': 1353, - 'color': '#193A7A', + name: 'Command', + freq: 1353, + color: '#193A7A', }, { - 'name': 'Medical', - 'freq': 1355, - 'color': '#008160', + name: 'Medical', + freq: 1355, + color: '#008160', }, { - 'name': 'Engineering', - 'freq': 1357, - 'color': '#A66300', + name: 'Engineering', + freq: 1357, + color: '#A66300', }, { - 'name': 'Security', - 'freq': 1359, - 'color': '#A30000', + name: 'Security', + freq: 1359, + color: '#A30000', }, { - 'name': 'Explorer', - 'freq': 1361, - 'color': '#555555', + name: 'Explorer', + freq: 1361, + color: '#555555', }, { - 'name': 'Talon', - 'freq': 1363, - 'color': '#555555', + name: 'Talon', + freq: 1363, + color: '#555555', }, { - 'name': 'Common', - 'freq': 1459, - 'color': '#008000', + name: 'Common', + freq: 1459, + color: '#008000', }, { - 'name': 'Entertainment', - 'freq': 1461, - 'color': '#339966', + name: 'Entertainment', + freq: 1461, + color: '#339966', }, { - 'name': 'Security(I)', - 'freq': 1475, - 'color': '#008000', + name: 'Security(I)', + freq: 1475, + color: '#008000', }, { - 'name': 'Medical(I)', - 'freq': 1485, - 'color': '#008000', + name: 'Medical(I)', + freq: 1485, + color: '#008000', }, ] as const; @@ -187,58 +187,58 @@ Entries must match /code/defines/gases.dm entries. */ const GASES = [ { - 'id': 'oxygen', - 'name': 'Oxygen', - 'label': 'O₂', - 'color': 'blue', + id: 'oxygen', + name: 'Oxygen', + label: 'O₂', + color: 'blue', }, { - 'id': 'nitrogen', - 'name': 'Nitrogen', - 'label': 'N₂', - 'color': 'green', + id: 'nitrogen', + name: 'Nitrogen', + label: 'N₂', + color: 'green', }, { - 'id': 'carbon_dioxide', - 'name': 'Carbon Dioxide', - 'label': 'CO₂', - 'color': 'grey', + id: 'carbon_dioxide', + name: 'Carbon Dioxide', + label: 'CO₂', + color: 'grey', }, { - 'id': 'phoron', - 'name': 'Phoron', - 'label': 'Phoron', - 'color': 'pink', + id: 'phoron', + name: 'Phoron', + label: 'Phoron', + color: 'pink', }, { - 'id': 'volatile_fuel', - 'name': 'Volatile Fuel', - 'label': 'EXP', - 'color': 'teal', + id: 'volatile_fuel', + name: 'Volatile Fuel', + label: 'EXP', + color: 'teal', }, { - 'id': 'nitrous_oxide', - 'name': 'Nitrous Oxide', - 'label': 'N₂O', - 'color': 'red', + id: 'nitrous_oxide', + name: 'Nitrous Oxide', + label: 'N₂O', + color: 'red', }, { - 'id': 'other', - 'name': 'Other', - 'label': 'Other', - 'color': 'white', + id: 'other', + name: 'Other', + label: 'Other', + color: 'white', }, { - 'id': 'pressure', - 'name': 'Pressure', - 'label': 'Pressure', - 'color': 'average', + id: 'pressure', + name: 'Pressure', + label: 'Pressure', + color: 'average', }, { - 'id': 'temperature', - 'name': 'Temperature', - 'label': 'Temperature', - 'color': 'yellow', + id: 'temperature', + name: 'Temperature', + label: 'Temperature', + color: 'yellow', }, ] as const; @@ -252,7 +252,7 @@ export const getGasLabel = (gasId: string, fallbackValue?: string) => { const gasSearchId = gasId.toLowerCase(); const gasSearchName = gasId.replace(/(^\w{1})|(\s+\w{1})/g, (letter) => - letter.toUpperCase() + letter.toUpperCase(), ); for (let idx = 0; idx < GASES.length; idx++) { @@ -272,7 +272,7 @@ export const getGasColor = (gasId: string) => { const gasSearchId = gasId.toLowerCase(); const gasSearchName = gasId.replace(/(^\w{1})|(\s+\w{1})/g, (letter) => - letter.toUpperCase() + letter.toUpperCase(), ); for (let idx = 0; idx < GASES.length; idx++) { @@ -292,7 +292,7 @@ export const getGasFromId = (gasId: string): Gas | undefined => { const gasSearchId = gasId.toLowerCase(); const gasSearchName = gasId.replace(/(^\w{1})|(\s+\w{1})/g, (letter) => - letter.toUpperCase() + letter.toUpperCase(), ); for (let idx = 0; idx < GASES.length; idx++) { diff --git a/tgui/packages/tgui/debug/KitchenSink.jsx b/tgui/packages/tgui/debug/KitchenSink.jsx index cd6ba33c42..d215ab777d 100644 --- a/tgui/packages/tgui/debug/KitchenSink.jsx +++ b/tgui/packages/tgui/debug/KitchenSink.jsx @@ -38,7 +38,8 @@ export const KitchenSink = (props) => { key={i} color="transparent" selected={i === pageIndex} - onClick={() => setPageIndex(i)}> + onClick={() => setPageIndex(i)} + > {story.meta.title} ))} diff --git a/tgui/packages/tgui/debug/hooks.js b/tgui/packages/tgui/debug/hooks.js index d324dd68d8..fc4901c496 100644 --- a/tgui/packages/tgui/debug/hooks.js +++ b/tgui/packages/tgui/debug/hooks.js @@ -4,7 +4,7 @@ * @license MIT */ -import { useSelector } from 'common/redux'; +import { useSelector } from '../backend'; import { selectDebug } from './selectors'; -export const useDebug = (context) => useSelector(context, selectDebug); +export const useDebug = () => useSelector(selectDebug); diff --git a/tgui/packages/tgui/debug/middleware.js b/tgui/packages/tgui/debug/middleware.js index 75687f2541..3f7414335e 100644 --- a/tgui/packages/tgui/debug/middleware.js +++ b/tgui/packages/tgui/debug/middleware.js @@ -5,9 +5,14 @@ */ import { KEY_BACKSPACE, KEY_F10, KEY_F11, KEY_F12 } from 'common/keycodes'; + import { globalEvents } from '../events'; import { acquireHotKey } from '../hotkeys'; -import { openExternalBrowser, toggleDebugLayout, toggleKitchenSink } from './actions'; +import { + openExternalBrowser, + toggleDebugLayout, + toggleKitchenSink, +} from './actions'; // prettier-ignore const relayedTypes = [ diff --git a/tgui/packages/tgui/drag.ts b/tgui/packages/tgui/drag.ts index 4b6f82e120..584666a97a 100644 --- a/tgui/packages/tgui/drag.ts +++ b/tgui/packages/tgui/drag.ts @@ -4,10 +4,10 @@ * @license MIT */ +import { storage } from 'common/storage'; import { vecAdd, vecMultiply, vecScale, vecSubtract } from 'common/vector'; import { createLogger } from './logging'; -import { storage } from 'common/storage'; const logger = createLogger('drag'); const pixelRatio = window.devicePixelRatio ?? 1; @@ -76,7 +76,7 @@ const getScreenSize = (): [number, number] => [ export const touchRecents = ( recents: string[], touchedItem: string, - limit = 50 + limit = 50, ): [string[], string | undefined] => { const nextRecents: string[] = [touchedItem]; let trimmedItem: string | undefined; @@ -105,7 +105,7 @@ const storeWindowGeometry = async () => { // Update the list of stored geometries const [geometries, trimmedKey] = touchRecents( (await storage.get('geometries')) || [], - windowKey + windowKey, ); if (trimmedKey) { storage.remove(trimmedKey); @@ -120,7 +120,7 @@ export const recallWindowGeometry = async ( pos?: [number, number]; size?: [number, number]; locked?: boolean; - } = {} + } = {}, ) => { const geometry = options.fancy && (await storage.get(windowKey)); if (geometry) { @@ -157,7 +157,7 @@ export const recallWindowGeometry = async ( pos = vecAdd( vecScale(areaAvailable, 0.5), vecScale(size, -0.5), - vecScale(screenOffset, -1.0) + vecScale(screenOffset, -1.0), ); setWindowPosition(pos); } @@ -182,7 +182,7 @@ export const setupDrag = async () => { */ const constraintPosition = ( pos: [number, number], - size: [number, number] + size: [number, number], ): [boolean, [number, number]] => { const screenPos = getScreenPosition(); const screenSize = getScreenSize(); @@ -203,12 +203,12 @@ const constraintPosition = ( }; // Start dragging the window -export const dragStartHandler = (event: MouseEvent) => { +export const dragStartHandler = (event) => { logger.log('drag start'); dragging = true; dragPointOffset = vecSubtract( [event.screenX, event.screenY], - getWindowPosition() + getWindowPosition(), ); // Focus click target (event.target as HTMLElement)?.focus(); @@ -218,7 +218,7 @@ export const dragStartHandler = (event: MouseEvent) => { }; // End dragging the window -const dragEndHandler = (event: MouseEvent) => { +const dragEndHandler = (event) => { logger.log('drag end'); dragMoveHandler(event); document.removeEventListener('mousemove', dragMoveHandler); @@ -234,7 +234,7 @@ const dragMoveHandler = (event: MouseEvent) => { } event.preventDefault(); setWindowPosition( - vecSubtract([event.screenX, event.screenY], dragPointOffset) + vecSubtract([event.screenX, event.screenY], dragPointOffset), ); }; @@ -246,7 +246,7 @@ export const resizeStartHandler = resizing = true; dragPointOffset = vecSubtract( [event.screenX, event.screenY], - getWindowPosition() + getWindowPosition(), ); initialSize = getWindowSize(); // Focus click target @@ -274,7 +274,7 @@ const resizeMoveHandler = (event: MouseEvent) => { event.preventDefault(); const currentOffset = vecSubtract( [event.screenX, event.screenY], - getWindowPosition() + getWindowPosition(), ); const delta = vecSubtract(currentOffset, dragPointOffset); // Extra 1x1 area is added to ensure the browser can see the cursor diff --git a/tgui/packages/tgui/events.test.ts b/tgui/packages/tgui/events.test.ts index 8f3e8e3109..e7a6c25018 100644 --- a/tgui/packages/tgui/events.test.ts +++ b/tgui/packages/tgui/events.test.ts @@ -1,4 +1,10 @@ -import { KeyEvent, addScrollableNode, canStealFocus, removeScrollableNode, setupGlobalEvents } from './events'; +import { + addScrollableNode, + canStealFocus, + KeyEvent, + removeScrollableNode, + setupGlobalEvents, +} from './events'; describe('focusEvents', () => { afterEach(() => { diff --git a/tgui/packages/tgui/events.ts b/tgui/packages/tgui/events.ts index 670f03e808..a31f4821d6 100644 --- a/tgui/packages/tgui/events.ts +++ b/tgui/packages/tgui/events.ts @@ -6,15 +6,14 @@ * @license MIT */ -import { KEY_ALT, KEY_CTRL, KEY_F1, KEY_F12, KEY_SHIFT } from 'common/keycodes'; - import { EventEmitter } from 'common/events'; +import { KEY_ALT, KEY_CTRL, KEY_F1, KEY_F12, KEY_SHIFT } from 'common/keycodes'; export const globalEvents = new EventEmitter(); let ignoreWindowFocus = false; export const setupGlobalEvents = ( - options: { ignoreWindowFocus?: boolean } = {} + options: { ignoreWindowFocus?: boolean } = {}, ): void => { ignoreWindowFocus = !!options.ignoreWindowFocus; }; diff --git a/tgui/packages/tgui/format.test.ts b/tgui/packages/tgui/format.test.ts index 011d9e9e6c..8fdc6683c4 100644 --- a/tgui/packages/tgui/format.test.ts +++ b/tgui/packages/tgui/format.test.ts @@ -1,4 +1,10 @@ -import { formatDb, formatMoney, formatSiBaseTenUnit, formatSiUnit, formatTime } from './format'; +import { + formatDb, + formatMoney, + formatSiBaseTenUnit, + formatSiUnit, + formatTime, +} from './format'; describe('formatSiUnit', () => { it('formats base values correctly', () => { diff --git a/tgui/packages/tgui/format.ts b/tgui/packages/tgui/format.ts index eb76bd2255..4ebc03e710 100644 --- a/tgui/packages/tgui/format.ts +++ b/tgui/packages/tgui/format.ts @@ -36,7 +36,7 @@ const SI_BASE_INDEX = SI_SYMBOLS.indexOf(' '); export const formatSiUnit = ( value: number, minBase1000 = -SI_BASE_INDEX, - unit = '' + unit = '', ): string => { if (!isFinite(value)) { return value.toString(); @@ -123,7 +123,7 @@ const SI_BASE_TEN_UNITS = [ export const formatSiBaseTenUnit = ( value: number, minBase1000 = 0, - unit = '' + unit = '', ): string => { if (!isFinite(value)) { return 'NaN'; @@ -147,7 +147,7 @@ export const formatSiBaseTenUnit = ( */ export const formatTime = ( val: number, - formatType: 'short' | 'default' = 'default' + formatType: 'short' | 'default' = 'default', ): string => { const totalSeconds = Math.floor(val / 10); const hours = Math.floor(totalSeconds / 3600); diff --git a/tgui/packages/tgui/hotkeys.ts b/tgui/packages/tgui/hotkeys.ts index 6e945cf4eb..426beb6d40 100644 --- a/tgui/packages/tgui/hotkeys.ts +++ b/tgui/packages/tgui/hotkeys.ts @@ -5,6 +5,7 @@ */ import * as keycodes from 'common/keycodes'; + import { globalEvents, KeyEvent } from './events'; import { createLogger } from './logging'; diff --git a/tgui/packages/tgui/http.ts b/tgui/packages/tgui/http.ts index a0ea97c3b6..b9dfe9c60c 100644 --- a/tgui/packages/tgui/http.ts +++ b/tgui/packages/tgui/http.ts @@ -4,7 +4,7 @@ export const fetchRetry = ( url: string, options?: RequestInit, - retryTimer: number = 1000 + retryTimer: number = 1000, ): Promise => { return fetch(url, options).catch(() => { return new Promise((resolve) => { diff --git a/tgui/packages/tgui/index.tsx b/tgui/packages/tgui/index.tsx index b1e6635d35..9f97a52d67 100644 --- a/tgui/packages/tgui/index.tsx +++ b/tgui/packages/tgui/index.tsx @@ -19,15 +19,15 @@ import './styles/themes/syndicate.scss'; import './styles/themes/wizard.scss'; import './styles/themes/abstract.scss'; -import { configureStore } from './store'; - -import { captureExternalLinks } from './links'; -import { createRenderer } from './renderer'; import { perf } from 'common/perf'; +import { setupHotReloading } from 'tgui-dev-server/link/client.cjs'; + +import { setGlobalStore } from './backend'; import { setupGlobalEvents } from './events'; import { setupHotKeys } from './hotkeys'; -import { setupHotReloading } from 'tgui-dev-server/link/client.cjs'; -import { setGlobalStore } from './backend'; +import { captureExternalLinks } from './links'; +import { createRenderer } from './renderer'; +import { configureStore } from './store'; perf.mark('inception', window.performance?.timing?.navigationStart); perf.mark('init'); diff --git a/tgui/packages/tgui/interfaces/AICard.jsx b/tgui/packages/tgui/interfaces/AICard.jsx index a2b9105812..62435777e0 100644 --- a/tgui/packages/tgui/interfaces/AICard.jsx +++ b/tgui/packages/tgui/interfaces/AICard.jsx @@ -18,7 +18,7 @@ export const AICard = (props) => { if (has_ai === 0) { return ( - +
@@ -49,7 +49,7 @@ export const AICard = (props) => { } return ( - +
diff --git a/tgui/packages/tgui/interfaces/APC.jsx b/tgui/packages/tgui/interfaces/APC.jsx index 29acd63819..410cf83d63 100644 --- a/tgui/packages/tgui/interfaces/APC.jsx +++ b/tgui/packages/tgui/interfaces/APC.jsx @@ -1,6 +1,14 @@ -import { Fragment } from 'inferno'; +import { Fragment } from 'react'; import { useBackend } from '../backend'; -import { Box, Button, Dimmer, Icon, LabeledList, ProgressBar, Section } from '../components'; +import { + Box, + Button, + Dimmer, + Icon, + LabeledList, + ProgressBar, + Section, +} from '../components'; import { Window } from '../layouts'; import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; import { FullscreenNotice } from './common/FullscreenNotice'; @@ -17,7 +25,7 @@ export const APC = (props) => { } return ( - + {body} ); @@ -77,16 +85,16 @@ const ApcContent = (props) => { const adjustedCellChange = data.powerCellStatus / 100; return ( - + <> + <> Fault in ID authenticator. Please contact maintenance for service. - + } />
@@ -103,7 +111,8 @@ const ApcContent = (props) => { disabled={locked} onClick={() => act('breaker')} /> - }> + } + > [ {externalPowerStatus.externalPowerText} ] @@ -120,7 +129,8 @@ const ApcContent = (props) => { disabled={locked} onClick={() => act('charge')} /> - }> + } + > [ {chargingStatus.chargingText} ] @@ -134,11 +144,12 @@ const ApcContent = (props) => { key={channel.title} label={channel.title} buttons={ - + <> = 2 ? 'good' : 'bad'}> + color={channel.status >= 2 ? 'good' : 'bad'} + > {channel.status >= 2 ? 'On' : 'Off'}
- + ); }; diff --git a/tgui/packages/tgui/interfaces/AccountsTerminal.jsx b/tgui/packages/tgui/interfaces/AccountsTerminal.jsx index cd24a0aa73..76e03af518 100644 --- a/tgui/packages/tgui/interfaces/AccountsTerminal.jsx +++ b/tgui/packages/tgui/interfaces/AccountsTerminal.jsx @@ -1,5 +1,13 @@ import { useBackend, useSharedState } from '../backend'; -import { Box, Button, LabeledList, Input, Section, Table, Tabs } from '../components'; +import { + Box, + Button, + LabeledList, + Input, + Section, + Table, + Tabs, +} from '../components'; import { Window } from '../layouts'; export const AccountsTerminal = (props) => { @@ -42,19 +50,22 @@ const AccountTerminalContent = (props) => { act('view_accounts_list')}> + onClick={() => act('view_accounts_list')} + > Home act('create_account')}> + onClick={() => act('create_account')} + > New Account act('print')}> + onClick={() => act('print')} + > Print @@ -121,7 +132,8 @@ const DetailedView = (props) => { content="Suspend" onClick={() => act('toggle_suspension')} /> - }> + } + > #{account_number} @@ -201,13 +213,14 @@ const ListView = (props) => { + key={acc.account_index} + > @@ -44,13 +47,15 @@ export const BombTester = (props) => { {(tank1 && ( )) || ( )} @@ -59,13 +64,15 @@ export const BombTester = (props) => { {(tank2 && ( )) || ( )} @@ -76,7 +83,8 @@ export const BombTester = (props) => { - }> + } + > {(canister && {canister}) || ( No tank connected. )} @@ -100,7 +108,8 @@ export const BombTester = (props) => { icon="bomb" fontSize={2} onClick={() => act('start_sim')} - fluid> + fluid + > Begin Simulation
@@ -177,15 +186,16 @@ class BombTesterSimulation extends Component { const newStyle = { position: 'relative', - 'left': x + 'px', - 'top': y + 'px', + left: x + 'px', + top: y + 'px', }; return (
+ style={{ overflow: 'hidden', width: '100%', height: '100%' }} + >
diff --git a/tgui/packages/tgui/interfaces/BotanyEditor.jsx b/tgui/packages/tgui/interfaces/BotanyEditor.jsx index 89ce6f8741..0b148e155a 100644 --- a/tgui/packages/tgui/interfaces/BotanyEditor.jsx +++ b/tgui/packages/tgui/interfaces/BotanyEditor.jsx @@ -9,7 +9,7 @@ export const BotanyEditor = (props) => { if (activity) { return ( - + Scanning... @@ -18,7 +18,7 @@ export const BotanyEditor = (props) => { } return ( - +
{(disk && ( diff --git a/tgui/packages/tgui/interfaces/BotanyIsolator.jsx b/tgui/packages/tgui/interfaces/BotanyIsolator.jsx index e1020b6153..92dba6ac3a 100644 --- a/tgui/packages/tgui/interfaces/BotanyIsolator.jsx +++ b/tgui/packages/tgui/interfaces/BotanyIsolator.jsx @@ -17,7 +17,7 @@ export const BotanyIsolator = (props) => { if (activity) { return ( - + Scanning... @@ -26,7 +26,7 @@ export const BotanyIsolator = (props) => { } return ( - +
{(hasGenetics && ( @@ -43,7 +43,8 @@ export const BotanyIsolator = (props) => { diff --git a/tgui/packages/tgui/interfaces/BrigTimer.jsx b/tgui/packages/tgui/interfaces/BrigTimer.jsx index 7ef7910cf9..2357c8464c 100644 --- a/tgui/packages/tgui/interfaces/BrigTimer.jsx +++ b/tgui/packages/tgui/interfaces/BrigTimer.jsx @@ -1,5 +1,5 @@ import { round } from 'common/math'; -import { Fragment } from 'inferno'; +import { Fragment } from 'react'; import { useBackend } from '../backend'; import { Button, Section, NumberInput, Flex } from '../components'; import { Window } from '../layouts'; @@ -8,12 +8,12 @@ import { formatTime } from '../format'; export const BrigTimer = (props) => { const { act, data } = useBackend(); return ( - +
+ <>
))} diff --git a/tgui/packages/tgui/interfaces/Canister.jsx b/tgui/packages/tgui/interfaces/Canister.jsx index 848b34ec2b..58c64e65cb 100644 --- a/tgui/packages/tgui/interfaces/Canister.jsx +++ b/tgui/packages/tgui/interfaces/Canister.jsx @@ -1,6 +1,16 @@ import { toFixed } from 'common/math'; import { useBackend } from '../backend'; -import { AnimatedNumber, Box, Button, Icon, Knob, LabeledControls, LabeledList, Section, Tooltip } from '../components'; +import { + AnimatedNumber, + Box, + Button, + Icon, + Knob, + LabeledControls, + LabeledList, + Section, + Tooltip, +} from '../components'; import { formatSiUnit } from '../format'; import { Window } from '../layouts'; @@ -18,7 +28,7 @@ export const Canister = (props) => { holding, } = data; return ( - +
{ content="Relabel" onClick={() => act('relabel')} /> - }> + } + > { onClick={() => act('eject')} /> ) - }> + } + > {!!holding && ( {holding.name} diff --git a/tgui/packages/tgui/interfaces/Canvas.jsx b/tgui/packages/tgui/interfaces/Canvas.jsx index e181ce3d31..1ef7b762c1 100644 --- a/tgui/packages/tgui/interfaces/Canvas.jsx +++ b/tgui/packages/tgui/interfaces/Canvas.jsx @@ -1,4 +1,4 @@ -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; import { useBackend } from '../backend'; import { Box, Button } from '../components'; import { Window } from '../layouts'; @@ -65,7 +65,8 @@ class PaintCanvas extends Component { width={width * dotsize || 300} height={height * dotsize || 300} {...rest} - onClick={(e) => this.clickwrapper(e)}> + onClick={(e) => this.clickwrapper(e)} + > Canvas failed to render. ); @@ -85,7 +86,8 @@ export const Canvas = (props) => { return ( + height={Math.min(700, height * dotsize + 72)} + > a - b, + Alphabetical: (a, b) => a - b, 'By availability': (a, b) => -(a.affordable - b.affordable), 'By price': (a, b) => a.price - b.price, }; export const CasinoPrizeDispenser = () => { return ( - + - + <> - + ); @@ -123,7 +131,8 @@ const CasinoPrizeDispenserItemsCategory = (properties) => { lineHeight="20px" style={{ float: 'left', - }}> + }} + > {item.name}
- + )}
@@ -113,7 +114,8 @@ const ViewCharacter = (props) => { content="Back" onClick={() => setOverlay(null)} /> - }> + } + >
{overlay.species}
@@ -158,7 +160,8 @@ const CharacterDirectoryList = (props) => { title="Directory" buttons={
- ) + ), )}
); diff --git a/tgui/packages/tgui/interfaces/CrewMonitor.jsx b/tgui/packages/tgui/interfaces/CrewMonitor.jsx index 4a53c596c0..6ca8cb56e7 100644 --- a/tgui/packages/tgui/interfaces/CrewMonitor.jsx +++ b/tgui/packages/tgui/interfaces/CrewMonitor.jsx @@ -3,7 +3,7 @@ import { flow } from 'common/fp'; import { useBackend, useLocalState } from '../backend'; import { Window } from '../layouts'; import { NanoMap, Box, Table, Button, Tabs, Icon } from '../components'; -import { Fragment } from 'inferno'; +import { Fragment } from 'react'; const getStatText = (cm) => { if (cm.dead) { @@ -29,7 +29,7 @@ const getStatColor = (cm) => { export const CrewMonitor = () => { return ( - + @@ -125,23 +125,25 @@ export const CrewMonitorContent = (props) => { } return ( - + <> setTabIndex(0)}> + onClick={() => setTabIndex(0)} + > Data View setTabIndex(1)}> + onClick={() => setTabIndex(1)} + > Map View {body} - + ); }; @@ -153,7 +155,7 @@ const CrewMonitorMapView = (props) => { setZoom(v)}> {data.crewmembers .filter( - (x) => x.sensor_type === 3 && ~~x.realZ === ~~config.mapZLevel + (x) => x.sensor_type === 3 && ~~x.realZ === ~~config.mapZLevel, ) .map((cm) => ( { isBeakerLoaded, } = data; return ( - + <>
{ - }> + } + > {hasOccupant ? ( @@ -71,13 +82,15 @@ const CryoContent = (props) => { min={occupant.health} max={occupant.maxHealth} value={occupant.health / occupant.maxHealth} - color={occupant.health > 0 ? 'good' : 'average'}> + color={occupant.health > 0 ? 'good' : 'average'} + > + color={statNames[occupant.stat][0]} + > {statNames[occupant.stat][1]} @@ -89,7 +102,8 @@ const CryoContent = (props) => { + ranges={{ bad: [0.01, Infinity] }} + > @@ -113,16 +127,19 @@ const CryoContent = (props) => { - }> + } + > @@ -134,7 +151,7 @@ const CryoContent = (props) => {
-
+ ); }; @@ -143,7 +160,7 @@ const CryoBeaker = (props) => { const { isBeakerLoaded, beakerLabel, beakerVolume } = data; if (isBeakerLoaded) { return ( - + <> {beakerLabel ? beakerLabel : No label} {beakerVolume ? ( @@ -155,7 +172,7 @@ const CryoBeaker = (props) => { 'Beaker is empty' )} - + ); } else { return No beaker loaded; diff --git a/tgui/packages/tgui/interfaces/CryoStorage.jsx b/tgui/packages/tgui/interfaces/CryoStorage.jsx index 10af1834cf..94b726643a 100644 --- a/tgui/packages/tgui/interfaces/CryoStorage.jsx +++ b/tgui/packages/tgui/interfaces/CryoStorage.jsx @@ -10,7 +10,7 @@ export const CryoStorage = (props) => { const [tab, setTab] = useLocalState('tab', 0); return ( - + setTab(0)}> @@ -59,13 +59,15 @@ export const CryoStorageItems = (props) => { - }> + } + > {(items.length && items.map((item) => ( ))) || No items stored.} diff --git a/tgui/packages/tgui/interfaces/CryoStorageVr.jsx b/tgui/packages/tgui/interfaces/CryoStorageVr.jsx index b1edb0e9c1..3d0b7755ab 100644 --- a/tgui/packages/tgui/interfaces/CryoStorageVr.jsx +++ b/tgui/packages/tgui/interfaces/CryoStorageVr.jsx @@ -11,7 +11,7 @@ export const CryoStorageVr = (props) => { const [tab, setTab] = useLocalState('tab', 0); return ( - + setTab(0)}> diff --git a/tgui/packages/tgui/interfaces/DNAForensics.jsx b/tgui/packages/tgui/interfaces/DNAForensics.jsx index 4426942bb1..64d297d04c 100644 --- a/tgui/packages/tgui/interfaces/DNAForensics.jsx +++ b/tgui/packages/tgui/interfaces/DNAForensics.jsx @@ -1,4 +1,4 @@ -import { Fragment } from 'inferno'; +import { Fragment } from 'react'; import { useBackend } from '../backend'; import { Box, Button, LabeledList, ProgressBar, Section } from '../components'; import { Window } from '../layouts'; @@ -12,22 +12,25 @@ export const DNAForensics = (props) => {
+ <> - - }> + + } + > { radiatingModal = ; } return ( - + {radiatingModal} @@ -50,7 +61,7 @@ const DNAModifierOccupant = (props) => {
+ <> Door Lock: @@ -67,10 +78,11 @@ const DNAModifierOccupant = (props) => { content="Eject" onClick={() => act('ejectOccupant')} /> - - }> + + } + > {hasOccupant ? ( - + <> {occupant.name} @@ -120,7 +132,7 @@ const DNAModifierOccupant = (props) => { )} - + ) : ( Cell unoccupied. )} @@ -159,17 +171,17 @@ const DNAModifierMain = (props) => { let body; if (selectedMenuKey === 'ui') { body = ( - + <> - + ); } else if (selectedMenuKey === 'se') { body = ( - + <> - + ); } else if (selectedMenuKey === 'buffer') { body = ; @@ -183,7 +195,8 @@ const DNAModifierMain = (props) => { act('selectMenuKey', { key: op[0] })}> + onClick={() => act('selectMenuKey', { key: op[0] })} + > {op[1]} @@ -305,12 +318,12 @@ const DNAModifierMainBuffers = (props) => { /> )); return ( - + <>
{bufferElements}
-
+ ); }; @@ -327,7 +340,7 @@ const DNAModifierMainBuffersElement = (props) => { mx="0" lineHeight="18px" buttons={ - + <> { }) } /> - - }> + + } + >
) : ( - + <>
{(effects && effects.map((effect) => ( @@ -95,7 +96,7 @@ const DiseaseSplicerVirusDish = (props) => { onClick={() => act('affected_species')} />
-
+ )}
); diff --git a/tgui/packages/tgui/interfaces/DishIncubator.jsx b/tgui/packages/tgui/interfaces/DishIncubator.jsx index 4f2770ae91..3260e7331e 100644 --- a/tgui/packages/tgui/interfaces/DishIncubator.jsx +++ b/tgui/packages/tgui/interfaces/DishIncubator.jsx @@ -1,7 +1,14 @@ -import { Fragment } from 'inferno'; +import { Fragment } from 'react'; import { formatCommaNumber } from '../format'; import { useBackend } from '../backend'; -import { Box, Button, Flex, LabeledList, ProgressBar, Section } from '../components'; +import { + Box, + Button, + Flex, + LabeledList, + ProgressBar, + Section, +} from '../components'; import { Window } from '../layouts'; export const DishIncubator = (props) => { @@ -37,7 +44,8 @@ export const DishIncubator = (props) => { content={on ? 'On' : 'Off'} onClick={() => act('power')} /> - }> + } + >
- + ); }; primaryRoutes['AirlockConsoleDocking'] = AirlockConsoleDocking; @@ -562,7 +572,7 @@ const DockingConsoleSimple = (props) => {
+ <>
- + ); }; primaryRoutes['DockingConsoleMulti'] = DockingConsoleMulti; @@ -646,7 +658,7 @@ const DoorAccessConsole = (props) => {
+ <> {/* Interior Button */}