Merge branch 'master' into PDAthing

This commit is contained in:
Runa Dacino
2024-02-08 16:14:43 +01:00
committed by GitHub
464 changed files with 8819 additions and 10923 deletions
+4 -1
View File
@@ -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
*/
+17 -1
View File
@@ -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)
+10 -1
View File
@@ -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'
+19
View File
@@ -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)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 654 KiB

After

Width:  |  Height:  |  Size: 655 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

+11 -1
View File
@@ -3,4 +3,14 @@
/**/*.bundle.*
/**/*.chunk.*
/**/*.hot-update.*
/packages/inferno/**
**.lock
**.log
**.json
**.svg
**.scss
**.md
**.css
**.txt
**.woff2
**.eot
**.ttf
-43
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
extends: 'plugin:sonarjs/recommended'
+31 -25
View File
@@ -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
+1
View File
@@ -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.
-14
View File
@@ -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
+15
View File
@@ -0,0 +1,15 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"jsc": {
"loose": true,
"parser": {
"syntax": "typescript",
"tsx": true
},
"transform": {
"react": {
"runtime": "automatic"
}
}
}
}
+4 -1
View File
@@ -2,5 +2,8 @@
"name": "eslint",
"version": "7.32.0-sdk",
"main": "./lib/api.js",
"type": "commonjs"
"type": "commonjs",
"bin": {
"eslint": "./bin/eslint.js"
}
}
+20
View File
@@ -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`);
-20
View File
@@ -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`);
+4 -3
View File
@@ -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"
}
+20
View File
@@ -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`);
+118 -69
View File
@@ -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;
+118 -69
View File
@@ -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;
+7 -7
View File
@@ -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`);
+5 -1
View File
@@ -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"
}
}
+3 -6
View File
@@ -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.
-44
View File
@@ -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;
+1 -1
View File
@@ -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
+6 -11
View File
@@ -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`
+38
View File
@@ -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.
*
+1 -1
View File
@@ -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,
+34 -40
View File
@@ -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"
}
}
+6 -6
View File
@@ -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;
+3 -3
View File
@@ -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,
);
/**
+5 -5
View File
@@ -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',
}
+3 -12
View File
@@ -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;
+9 -2
View File
@@ -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);
});
+9 -29
View File
@@ -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());
};
+3 -3
View File
@@ -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)),
);
}
};
+2 -1
View File
@@ -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');
}
+3 -1
View File
@@ -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) => {
+1 -1
View File
@@ -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;
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -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>,
);
}
+4 -3
View File
@@ -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);
}
+2 -2
View File
@@ -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 -33
View File
@@ -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) {
+4 -3
View File
@@ -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');
+5 -5
View File
@@ -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"
}
}
+8 -7
View File
@@ -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.
+2 -1
View File
@@ -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');
+5 -4
View File
@@ -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');
+4 -3
View File
@@ -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 -5
View File
@@ -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>
+5 -4
View File
@@ -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,
})
}),
)
}>
}
>
&laquo;
</Button>
<Button
@@ -71,9 +86,10 @@ export const ChatPageSettings = (props, context) => {
dispatch(
moveChatPageRight({
pageId: page.id,
})
}),
)
}>
}
>
&raquo;
</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>
))}
+8 -7
View File
@@ -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>
)}
+16 -14
View File
@@ -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>
))}
+1
View File
@@ -5,6 +5,7 @@
*/
import { createAction } from 'common/redux';
import { createPage } from './model';
export const loadChat = createAction('chat/load');
+27 -7
View File
@@ -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;
+2 -1
View File
@@ -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];
+12 -1
View File
@@ -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();
+27 -14
View File
@@ -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 -3
View File
@@ -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);
};
+1 -1
View File
@@ -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,
+9 -12
View File
@@ -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();
}
},
);
}
};
+3 -2
View File
@@ -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),
+7 -2
View File
@@ -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,
+10 -6
View File
@@ -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,
})
}),
)
}
/>
+4 -3
View File
@@ -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',
);
+6 -5
View File
@@ -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,

Some files were not shown because too many files have changed in this diff Show More