Merge branch 'master' into PDAthing
@@ -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,
|
||||
|
||||
@@ -155,4 +155,28 @@ Talon pin
|
||||
|
||||
/datum/gear/accessory/antediluvian/loin
|
||||
display_name = "antediluvian loincloth"
|
||||
path = /obj/item/clothing/accessory/antediluvian/loincloth
|
||||
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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
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))
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
|
||||
|
||||
@@ -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"
|
||||
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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
color_blend_mode = ICON_MULTIPLY
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 654 KiB After Width: | Height: | Size: 655 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 199 KiB |
|
After Width: | Height: | Size: 437 B |
|
After Width: | Height: | Size: 575 B |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
@@ -3,4 +3,14 @@
|
||||
/**/*.bundle.*
|
||||
/**/*.chunk.*
|
||||
/**/*.hot-update.*
|
||||
/packages/inferno/**
|
||||
**.lock
|
||||
**.log
|
||||
**.json
|
||||
**.svg
|
||||
**.scss
|
||||
**.md
|
||||
**.css
|
||||
**.txt
|
||||
**.woff2
|
||||
**.eot
|
||||
**.ttf
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
extends: 'plugin:sonarjs/recommended'
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"jsc": {
|
||||
"loose": true,
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true
|
||||
},
|
||||
"transform": {
|
||||
"react": {
|
||||
"runtime": "automatic"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,8 @@
|
||||
"name": "eslint",
|
||||
"version": "7.32.0-sdk",
|
||||
"main": "./lib/api.js",
|
||||
"type": "commonjs"
|
||||
"type": "commonjs",
|
||||
"bin": {
|
||||
"eslint": "./bin/eslint.js"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
@@ -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`);
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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`);
|
||||
@@ -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 /<pwd>/zipfile:/<pwd>/.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 /<pwd>/zipfile:/<pwd>/.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:///<pwd>/.yarn/...
|
||||
return str.replace(/^zipfile:\/\//, ``);
|
||||
} break;
|
||||
case `neovim`:
|
||||
{
|
||||
str = str.replace(/\.zip::/, `.zip/`);
|
||||
// The path for neovim is in format of zipfile:///<pwd>/.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;
|
||||
|
||||
@@ -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 /<pwd>/zipfile:/<pwd>/.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 /<pwd>/zipfile:/<pwd>/.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:///<pwd>/.yarn/...
|
||||
return str.replace(/^zipfile:\/\//, ``);
|
||||
} break;
|
||||
case `neovim`:
|
||||
{
|
||||
str = str.replace(/\.zip::/, `.zip/`);
|
||||
// The path for neovim is in format of zipfile:///<pwd>/.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;
|
||||
|
||||
@@ -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`);
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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> = [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.
|
||||
*
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,12 +32,12 @@ export const filter =
|
||||
};
|
||||
|
||||
type MapFunction = {
|
||||
<T, U>(iterateeFn: (value: T, index: number, collection: T[]) => U): (
|
||||
collection: T[]
|
||||
) => U[];
|
||||
<T, U>(
|
||||
iterateeFn: (value: T, index: number, collection: T[]) => U,
|
||||
): (collection: T[]) => U[];
|
||||
|
||||
<T, U, K extends string | number>(
|
||||
iterateeFn: (value: T, index: K, collection: Record<K, T>) => U
|
||||
iterateeFn: (value: T, index: K, collection: Record<K, T>) => U,
|
||||
): (collection: Record<K, T>) => U[];
|
||||
};
|
||||
|
||||
@@ -75,7 +75,7 @@ export const map: MapFunction =
|
||||
*/
|
||||
export const filterMap = <T, U>(
|
||||
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 = <T, U = unknown>(
|
||||
getKey: (value: T) => U,
|
||||
collection: readonly T[],
|
||||
inserting: T
|
||||
inserting: T,
|
||||
): number => {
|
||||
if (collection.length === 0) {
|
||||
return 0;
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<number, Action<string>> = (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);
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
export type Reducer<State = any, ActionType extends Action = AnyAction> = (
|
||||
state: State | undefined,
|
||||
action: ActionType
|
||||
action: ActionType,
|
||||
) => State;
|
||||
|
||||
export type Store<State = any, ActionType extends Action = AnyAction> = {
|
||||
@@ -21,7 +21,7 @@ type MiddlewareAPI<State = any, ActionType extends Action = AnyAction> = {
|
||||
};
|
||||
|
||||
export type Middleware = <State = any, ActionType extends Action = AnyAction>(
|
||||
storeApi: MiddlewareAPI<State, ActionType>
|
||||
storeApi: MiddlewareAPI<State, ActionType>,
|
||||
) => (next: Dispatch<ActionType>) => Dispatch<ActionType>;
|
||||
|
||||
export type Action<TType = any> = {
|
||||
@@ -33,7 +33,7 @@ export type AnyAction = Action & {
|
||||
};
|
||||
|
||||
export type Dispatch<ActionType extends Action = AnyAction> = (
|
||||
action: ActionType
|
||||
action: ActionType,
|
||||
) => void;
|
||||
|
||||
type StoreEnhancer = (createStoreFunction: Function) => Function;
|
||||
@@ -48,7 +48,7 @@ type PreparedAction = {
|
||||
*/
|
||||
export const createStore = <State, ActionType extends Action = AnyAction>(
|
||||
reducer: Reducer<State, ActionType>,
|
||||
enhancer?: StoreEnhancer
|
||||
enhancer?: StoreEnhancer,
|
||||
): Store<State, ActionType> => {
|
||||
// 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<string, Reducer>
|
||||
reducersObj: Record<string, Reducer>,
|
||||
): Reducer => {
|
||||
const keys = Object.keys(reducersObj);
|
||||
|
||||
@@ -170,7 +170,7 @@ export const combineReducers = (
|
||||
*/
|
||||
export const createAction = <TAction extends string>(
|
||||
type: TAction,
|
||||
prepare?: (...args: any[]) => PreparedAction
|
||||
prepare?: (...args: any[]) => PreparedAction,
|
||||
) => {
|
||||
const actionCreator = (...args: any[]) => {
|
||||
let action: Action<TAction> & PreparedAction = { type };
|
||||
@@ -194,23 +194,3 @@ export const createAction = <TAction extends string>(
|
||||
|
||||
return actionCreator;
|
||||
};
|
||||
|
||||
// Implementation specific
|
||||
// --------------------------------------------------------
|
||||
|
||||
export const useDispatch = <TAction extends Action = AnyAction>(context: {
|
||||
store: Store<unknown, TAction>;
|
||||
}): Dispatch<TAction> => {
|
||||
return context?.store?.dispatch;
|
||||
};
|
||||
|
||||
export const useSelector = <State, Selected>(
|
||||
context: { store: Store<State, Action> },
|
||||
selector: (state: State) => Selected
|
||||
): Selected => {
|
||||
if (!context) {
|
||||
return {} as Selected;
|
||||
}
|
||||
|
||||
return selector(context?.store?.getState());
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
export const debounce = <F extends (...args: any[]) => any>(
|
||||
fn: F,
|
||||
time: number,
|
||||
immediate = false
|
||||
immediate = false,
|
||||
): ((...args: Parameters<F>) => void) => {
|
||||
let timeout: ReturnType<typeof setTimeout> | null;
|
||||
return (...args: Parameters<F>) => {
|
||||
@@ -38,7 +38,7 @@ export const debounce = <F extends (...args: any[]) => any>(
|
||||
*/
|
||||
export const throttle = <F extends (...args: any[]) => any>(
|
||||
fn: F,
|
||||
time: number
|
||||
time: number,
|
||||
): ((...args: Parameters<F>) => void) => {
|
||||
let previouslyRun: number | null,
|
||||
queuedToRun: ReturnType<typeof setTimeout> | null;
|
||||
@@ -53,7 +53,7 @@ export const throttle = <F extends (...args: any[]) => any>(
|
||||
} else {
|
||||
queuedToRun = setTimeout(
|
||||
() => invokeFn(...args),
|
||||
time - (now - (previouslyRun ?? 0))
|
||||
time - (now - (previouslyRun ?? 0)),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -27,7 +27,7 @@ declare class Benchmark {
|
||||
static reduce<T, K>(
|
||||
arr: T[],
|
||||
callback: (accumulator: K, value: T) => K,
|
||||
thisArg?: any
|
||||
thisArg?: any,
|
||||
): K;
|
||||
|
||||
static options: Benchmark.Options;
|
||||
|
||||
@@ -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:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = <Button>Hello world!</Button>;
|
||||
render(node);
|
||||
@@ -16,13 +13,6 @@ export const SingleButtonWithCallback = () => {
|
||||
render(node);
|
||||
};
|
||||
|
||||
export const SingleButtonWithLinkEvent = () => {
|
||||
const node = (
|
||||
<Button onClick={linkEvent(null, handleClick)}>Hello world!</Button>
|
||||
);
|
||||
render(node);
|
||||
};
|
||||
|
||||
export const ListOfButtons = () => {
|
||||
const nodes: JSX.Element[] = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
@@ -45,19 +35,6 @@ export const ListOfButtonsWithCallback = () => {
|
||||
render(<div>{nodes}</div>);
|
||||
};
|
||||
|
||||
export const ListOfButtonsWithLinkEvent = () => {
|
||||
const nodes: JSX.Element[] = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const node = (
|
||||
<Button key={i} onClick={linkEvent(null, handleClick)}>
|
||||
Hello world! {i}
|
||||
</Button>
|
||||
);
|
||||
nodes.push(node);
|
||||
}
|
||||
render(<div>{nodes}</div>);
|
||||
};
|
||||
|
||||
export const ListOfButtonsWithIcons = () => {
|
||||
const nodes: JSX.Element[] = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
|
||||
@@ -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 (
|
||||
<StoreProvider store={store}>
|
||||
<DisposalBin />
|
||||
</StoreProvider>
|
||||
}),
|
||||
);
|
||||
return <DisposalBin />;
|
||||
});
|
||||
|
||||
export const data = JSON.stringify({
|
||||
|
||||
@@ -12,7 +12,7 @@ export const ListOfTooltips = () => {
|
||||
<Box as="span" backgroundColor="blue" fontSize="48px" m={1}>
|
||||
Tooltip #{i}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Tooltip>,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 <KitchenSink panel />;
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
}}
|
||||
>
|
||||
{
|
||||
<Collapsible title={title || 'Unknown Track'} color={'blue'}>
|
||||
<Section>
|
||||
|
||||
@@ -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' }),
|
||||
|
||||
@@ -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 (
|
||||
<Section>
|
||||
<Stack align="center">
|
||||
@@ -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
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
@@ -60,9 +74,10 @@ export const ChatPageSettings = (props, context) => {
|
||||
dispatch(
|
||||
moveChatPageLeft({
|
||||
pageId: page.id,
|
||||
})
|
||||
}),
|
||||
)
|
||||
}>
|
||||
}
|
||||
>
|
||||
«
|
||||
</Button>
|
||||
<Button
|
||||
@@ -71,9 +86,10 @@ export const ChatPageSettings = (props, context) => {
|
||||
dispatch(
|
||||
moveChatPageRight({
|
||||
pageId: page.id,
|
||||
})
|
||||
}),
|
||||
)
|
||||
}>
|
||||
}
|
||||
>
|
||||
»
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
@@ -84,7 +100,7 @@ export const ChatPageSettings = (props, context) => {
|
||||
<Divider />
|
||||
<Section title="Messages to display" level={2}>
|
||||
{MESSAGE_TYPES.filter(
|
||||
(typeDef) => !typeDef.important && !typeDef.admin
|
||||
(typeDef) => !typeDef.important && !typeDef.admin,
|
||||
).map((typeDef) => (
|
||||
<Button.Checkbox
|
||||
key={typeDef.type}
|
||||
@@ -94,15 +110,16 @@ export const ChatPageSettings = (props, context) => {
|
||||
toggleAcceptedType({
|
||||
pageId: page.id,
|
||||
type: typeDef.type,
|
||||
})
|
||||
}),
|
||||
)
|
||||
}>
|
||||
}
|
||||
>
|
||||
{typeDef.name}
|
||||
</Button.Checkbox>
|
||||
))}
|
||||
<Collapsible mt={1} color="transparent" title="Admin stuff">
|
||||
{MESSAGE_TYPES.filter(
|
||||
(typeDef) => !typeDef.important && typeDef.admin
|
||||
(typeDef) => !typeDef.important && typeDef.admin,
|
||||
).map((typeDef) => (
|
||||
<Button.Checkbox
|
||||
key={typeDef.type}
|
||||
@@ -112,9 +129,10 @@ export const ChatPageSettings = (props, context) => {
|
||||
toggleAcceptedType({
|
||||
pageId: page.id,
|
||||
type: typeDef.type,
|
||||
})
|
||||
}),
|
||||
)
|
||||
}>
|
||||
}
|
||||
>
|
||||
{typeDef.name}
|
||||
</Button.Checkbox>
|
||||
))}
|
||||
|
||||
@@ -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 {
|
||||
<Button
|
||||
className="Chat__scrollButton"
|
||||
icon="arrow-down"
|
||||
onClick={() => chatRenderer.scrollToBottom()}>
|
||||
onClick={() => chatRenderer.scrollToBottom()}
|
||||
>
|
||||
Scroll to bottom
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -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 }) => (
|
||||
<Box
|
||||
style={{
|
||||
'font-size': '0.7em',
|
||||
'border-radius': '0.25em',
|
||||
'width': '1.7em',
|
||||
'line-height': '1.55em',
|
||||
'background-color': 'crimson',
|
||||
'color': '#fff',
|
||||
}}>
|
||||
fontSize: '0.7em',
|
||||
borderRadius: '0.25em',
|
||||
width: '1.7em',
|
||||
lineHeight: '1.55em',
|
||||
backgroundColor: 'crimson',
|
||||
color: '#fff',
|
||||
}}
|
||||
>
|
||||
{Math.min(value, 99)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
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 (
|
||||
<Flex align="center">
|
||||
<Flex.Item>
|
||||
@@ -45,9 +46,10 @@ export const ChatTabs = (props, context) => {
|
||||
dispatch(
|
||||
changeChatPage({
|
||||
pageId: page.id,
|
||||
})
|
||||
}),
|
||||
)
|
||||
}>
|
||||
}
|
||||
>
|
||||
{page.name}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { createAction } from 'common/redux';
|
||||
|
||||
import { createPage } from './model';
|
||||
|
||||
export const loadChat = createAction('chat/load');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
<Element {...outputProps}>
|
||||
<span dangerouslySetInnerHTML={oldHtml} />
|
||||
</Element>,
|
||||
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);
|
||||
|
||||
@@ -149,7 +149,7 @@ export const highlightNode = (
|
||||
node,
|
||||
regex,
|
||||
words,
|
||||
createNode = createHighlightNode
|
||||
createNode = createHighlightNode,
|
||||
) => {
|
||||
if (!createNode) {
|
||||
createNode = createHighlightNode;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
<StoreProvider store={store}>
|
||||
<Panel />
|
||||
</StoreProvider>
|
||||
);
|
||||
return <Panel />;
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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:*"
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<Button
|
||||
color="white"
|
||||
onClick={() => {
|
||||
Byond.command('.reconnect');
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
Reconnect
|
||||
</Button>
|
||||
<Button
|
||||
@@ -32,14 +34,16 @@ export const ReconnectButton = (props, context) => {
|
||||
onClick={() => {
|
||||
location.href = `byond://${url}`;
|
||||
Byond.command('.quit');
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
Relaunch game
|
||||
</Button>
|
||||
<Button
|
||||
color="white"
|
||||
onClick={() => {
|
||||
dispatch(dismissWarning());
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
Dismiss
|
||||
</Button>
|
||||
</>
|
||||
|
||||
@@ -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 (
|
||||
<Stack fill>
|
||||
<Stack.Item>
|
||||
@@ -31,9 +60,10 @@ export const SettingsPanel = (props, context) => {
|
||||
dispatch(
|
||||
changeSettingsTab({
|
||||
tabId: tab.id,
|
||||
})
|
||||
}),
|
||||
)
|
||||
}>
|
||||
}
|
||||
>
|
||||
{tab.name}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
@@ -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 (
|
||||
<Section>
|
||||
<LabeledList>
|
||||
@@ -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 (
|
||||
<Section>
|
||||
<LabeledList>
|
||||
@@ -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 (
|
||||
<Section>
|
||||
<LabeledList>
|
||||
@@ -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?'}
|
||||
</Button>
|
||||
) : (
|
||||
@@ -334,7 +359,8 @@ export const ExportTab = (props, context) => {
|
||||
setTimeout(() => {
|
||||
setPurgeConfirm(false);
|
||||
}, 5000);
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
Purge message archive
|
||||
</Button>
|
||||
)}
|
||||
@@ -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 (
|
||||
<Section fill scrollable height="200px">
|
||||
<Section p={0}>
|
||||
@@ -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,
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||