diff --git a/.vscode/settings.json b/.vscode/settings.json index 6ceb9cb8d4..6f42af3c40 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,13 +1,19 @@ { "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": { "**/.yarn": true, "**/.pnp.*": true }, + "workbench.editorAssociations": { + "*.dmi": "imagePreview.previewEditor" + }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, "files.eol": "\n", "files.encoding": "utf8", "files.insertFinalNewline": true, diff --git a/code/ZAS/Connection.dm b/code/ZAS/Connection.dm index b780679d66..21be29d25e 100644 --- a/code/ZAS/Connection.dm +++ b/code/ZAS/Connection.dm @@ -165,4 +165,8 @@ Class Procs: return - //to_world("valid.") \ No newline at end of file + //to_world("valid.") + +#undef CONNECTION_DIRECT +#undef CONNECTION_SPACE +#undef CONNECTION_INVALID diff --git a/code/ZAS/Fire.dm b/code/ZAS/Fire.dm index 1728a95430..5c3a24d882 100644 --- a/code/ZAS/Fire.dm +++ b/code/ZAS/Fire.dm @@ -6,8 +6,6 @@ The more pressure, the more boom. If it gains pressure too slowly, it may leak or just rupture instead of exploding. */ -//#define FIREDBG - /turf/var/obj/fire/fire = null //Some legacy definitions so fires can be started. diff --git a/code/__datastructures/globals.dm b/code/__defines/__globals.dm similarity index 60% rename from code/__datastructures/globals.dm rename to code/__defines/__globals.dm index a51bad06db..d355da20bc 100644 --- a/code/__datastructures/globals.dm +++ b/code/__defines/__globals.dm @@ -1,61 +1,61 @@ // See also controllers/globals.dm -// Creates a global initializer with a given InitValue expression, do not use outside this file +/// Creates a global initializer with a given InitValue expression, do not use #define GLOBAL_MANAGED(X, InitValue)\ /datum/controller/global_vars/proc/InitGlobal##X(){\ ##X = ##InitValue;\ gvars_datum_init_order += #X;\ } -// Creates an empty global initializer, do not use outside this file +/// Creates an empty global initializer, do not use #define GLOBAL_UNMANAGED(X) /datum/controller/global_vars/proc/InitGlobal##X() { return; } -// Prevents a given global from being VV'd +/// Prevents a given global from being VV'd #ifndef TESTING #define GLOBAL_PROTECT(X)\ /datum/controller/global_vars/InitGlobal##X(){\ ..();\ - gvars_datum_protected_varlist += #X;\ + gvars_datum_protected_varlist[#X] = TRUE;\ } #else #define GLOBAL_PROTECT(X) #endif -// Standard BYOND global, do not use outside this file +/// Standard BYOND global, seriously do not use without an earthshakingly good reason #define GLOBAL_REAL_VAR(X) var/global/##X -// Standard typed BYOND global, do not use outside this file + +/// Standard typed BYOND global, seriously do not use without an earthshakingly good reason #define GLOBAL_REAL(X, Typepath) var/global##Typepath/##X -// Defines a global var on the controller, do not use outside this file. +/// Defines a global var on the controller, do not use #define GLOBAL_RAW(X) /datum/controller/global_vars/var/global##X -// Create an untyped global with an initializer expression +/// Create an untyped global with an initializer expression #define GLOBAL_VAR_INIT(X, InitValue) GLOBAL_RAW(/##X); GLOBAL_MANAGED(X, InitValue) -// Create a global const var, do not use +/// Create a global const var, do not use #define GLOBAL_VAR_CONST(X, InitValue) GLOBAL_RAW(/const/##X) = InitValue; GLOBAL_UNMANAGED(X) -// Create a list global with an initializer expression +/// Create a list global with an initializer expression #define GLOBAL_LIST_INIT(X, InitValue) GLOBAL_RAW(/list/##X); GLOBAL_MANAGED(X, InitValue) -// Create a list global that is initialized as an empty list +/// Create a list global that is initialized as an empty list #define GLOBAL_LIST_EMPTY(X) GLOBAL_LIST_INIT(X, list()) -// Create a typed list global with an initializer expression +/// Create a typed list global with an initializer expression #define GLOBAL_LIST_INIT_TYPED(X, Typepath, InitValue) GLOBAL_RAW(/list##Typepath/X); GLOBAL_MANAGED(X, InitValue) -// Create a typed list global that is initialized as an empty list +/// Create a typed list global that is initialized as an empty list #define GLOBAL_LIST_EMPTY_TYPED(X, Typepath) GLOBAL_LIST_INIT_TYPED(X, Typepath, list()) -// Create a typed global with an initializer expression +/// Create a typed global with an initializer expression #define GLOBAL_DATUM_INIT(X, Typepath, InitValue) GLOBAL_RAW(Typepath/##X); GLOBAL_MANAGED(X, InitValue) -// Create an untyped null global +/// Create an untyped null global #define GLOBAL_VAR(X) GLOBAL_RAW(/##X); GLOBAL_UNMANAGED(X) -// Create a null global list +/// Create a null global list #define GLOBAL_LIST(X) GLOBAL_RAW(/list/##X); GLOBAL_UNMANAGED(X) -// Create a typed null global +/// Create a typed null global #define GLOBAL_DATUM(X, Typepath) GLOBAL_RAW(Typepath/##X); GLOBAL_UNMANAGED(X) - diff --git a/code/__defines/_compile_options.dm b/code/__defines/_compile_options.dm index 50ff3fbf2b..83baac198f 100644 --- a/code/__defines/_compile_options.dm +++ b/code/__defines/_compile_options.dm @@ -8,6 +8,7 @@ */ // ZAS Compile Options +//#define FIREDBG // Uncomment to turn on ZAS debugging related to fire stuff. //#define ZASDBG // Uncomment to turn on super detailed ZAS debugging that probably won't even compile. #define MULTIZAS // Uncomment to turn on Multi-Z ZAS Support! diff --git a/code/__defines/admin_vr.dm b/code/__defines/admin_vr.dm index 01226df1bd..299b290699 100644 --- a/code/__defines/admin_vr.dm +++ b/code/__defines/admin_vr.dm @@ -13,4 +13,7 @@ #define MODIFIY_ROBOT_RADIOC_REMOVE "Remove a Radio Channel" #define MODIFIY_ROBOT_COMP_ADD "Replace a Component" #define MODIFIY_ROBOT_COMP_REMOVE "Remove a Component" +#define MODIFIY_ROBOT_SWAP_MODULE "Swap a Robot Module" #define MODIFIY_ROBOT_RESET_MODULE "Fully Reset Robot Module" +#define MODIFIY_ROBOT_TOGGLE_ERT "Toggle ERT Module Overwrite" +#define MODIFIY_ROBOT_TOGGLE_CENT_ACCESS "Toggle Central Access Codes" diff --git a/code/__defines/chat.dm b/code/__defines/chat.dm index 34df707e74..681fc8867b 100644 --- a/code/__defines/chat.dm +++ b/code/__defines/chat.dm @@ -12,6 +12,7 @@ #define MESSAGE_TYPE_LOCALCHAT "localchat" #define MESSAGE_TYPE_NPCEMOTE "npcemote" #define MESSAGE_TYPE_PLOCALCHAT "plocalchat" +#define MESSAGE_TYPE_HIVEMIND "hivemind" #define MESSAGE_TYPE_RADIO "radio" #define MESSAGE_TYPE_NIF "nif" #define MESSAGE_TYPE_INFO "info" diff --git a/code/__defines/projectiles.dm b/code/__defines/projectiles.dm new file mode 100644 index 0000000000..34a3008e76 --- /dev/null +++ b/code/__defines/projectiles.dm @@ -0,0 +1,10 @@ +//Designed for things that need precision trajectories like projectiles. +//Don't use this for anything that you don't absolutely have to use this with (like projectiles!) because it isn't worth using a datum unless you need accuracy down to decimal places in pixels. + +//You might see places where it does - 16 - 1. This is intentionally 17 instead of 16, because of how byond's tiles work and how not doing it will result in rounding errors like things getting put on the wrong turf. + +#define RETURN_PRECISE_POSITION(A) new /datum/position(A) +#define RETURN_PRECISE_POINT(A) new /datum/point(A) + +#define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED)) +#define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT)) diff --git a/code/__defines/span_vr.dm b/code/__defines/span_vr.dm index 2793d0546f..eaf45491cd 100644 --- a/code/__defines/span_vr.dm +++ b/code/__defines/span_vr.dm @@ -22,14 +22,19 @@ #define span_srvradio(str) ("" + str + "") #define span_expradio(str) ("" + str + "") +#define span_binary(str) ("" + str + "") +#define span_hivemind(str) ("" + str + "") + #define span_name(str) ("" + str + "") #define span_say(str) ("" + str + "") #define span_alert(str) ("" + str + "") #define span_ghostalert(str) ("" + str + "") +#define span_npc_say(str) ("" + str + "") #define span_emote(str) ("" + str + "") #define span_emote_subtle(str) ("" + str + "") +#define span_npc_emote(str) ("" + str + "") #define span_attack(str) ("" + str + "") #define span_moderate(str) ("" + str + "") diff --git a/code/__defines/vore.dm b/code/__defines/vore.dm new file mode 100644 index 0000000000..63859a80e4 --- /dev/null +++ b/code/__defines/vore.dm @@ -0,0 +1,15 @@ +#define VORE_SOUND_FALLOFF 0.1 +#define VORE_SOUND_RANGE 3 + +#define HOLO_ORIGINAL_COLOR null //Original hologram color: "#7db4e1" +#define HOLO_HARDLIGHT_COLOR "#d97de0" +#define HOLO_ORIGINAL_ALPHA 120 +#define HOLO_HARDLIGHT_ALPHA 200 + +#define BELLIES_MAX 40 +#define BELLIES_NAME_MIN 2 +#define BELLIES_NAME_MAX 40 +#define BELLIES_DESC_MAX 4096 +#define FLAVOR_MAX 400 + +#define VORE_VERSION 2 //This is a Define so you don't have to worry about magic numbers. diff --git a/code/_global_vars/sensitive.dm b/code/_global_vars/sensitive.dm index d4eda095ad..8de09f4f3a 100644 --- a/code/_global_vars/sensitive.dm +++ b/code/_global_vars/sensitive.dm @@ -8,4 +8,4 @@ GLOBAL_REAL_VAR(sqlpass) = "" GLOBAL_REAL_VAR(sqlfdbkdb) = "test" GLOBAL_REAL_VAR(sqlfdbklogin) = "root" GLOBAL_REAL_VAR(sqlfdbkpass) = "" -GLOBAL_REAL_VAR(sqllogging) = 0 // Should we log deaths, population stats, etc.? \ No newline at end of file +GLOBAL_REAL_VAR(sqllogging) = 0 // Should we log deaths, population stats, etc.? diff --git a/code/datums/chat_message.dm b/code/datums/chat_message.dm index 66dbc11440..4dd8d42537 100644 --- a/code/datums/chat_message.dm +++ b/code/datums/chat_message.dm @@ -327,6 +327,11 @@ var/list/runechat_image_cache = list() if(5) return rgb(c,m,x) +#undef CM_COLOR_SAT_MIN +#undef CM_COLOR_SAT_MAX +#undef CM_COLOR_LUM_MIN +#undef CM_COLOR_LUM_MAX + /atom/proc/runechat_message(message, range = world.view, italics, list/classes = list(), audible = TRUE, list/specific_viewers) var/hearing_mobs if(islist(specific_viewers)) @@ -369,3 +374,22 @@ var/list/runechat_image_cache = list() if(istype(loc, /obj/item/weapon/holder)) return loc return ..() + +#undef CHAT_MESSAGE_SPAWN_TIME +#undef CHAT_MESSAGE_LIFESPAN +#undef CHAT_MESSAGE_EOL_FADE +#undef CHAT_MESSAGE_EXP_DECAY +#undef CHAT_MESSAGE_HEIGHT_DECAY +#undef CHAT_MESSAGE_APPROX_LHEIGHT + +#undef CHAT_MESSAGE_WIDTH +#undef CHAT_MESSAGE_EXT_WIDTH +#undef CHAT_MESSAGE_LENGTH +#undef CHAT_MESSAGE_EXT_LENGTH + +#undef CHAT_MESSAGE_MOB +#undef CHAT_MESSAGE_OBJ +#undef WXH_TO_HEIGHT + +#undef CHAT_RUNE_EMOTE +#undef CHAT_RUNE_RADIO diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm index eb5127aed6..dcef0298d4 100644 --- a/code/datums/position_point_vector.dm +++ b/code/datums/position_point_vector.dm @@ -1,14 +1,3 @@ -//Designed for things that need precision trajectories like projectiles. -//Don't use this for anything that you don't absolutely have to use this with (like projectiles!) because it isn't worth using a datum unless you need accuracy down to decimal places in pixels. - -//You might see places where it does - 16 - 1. This is intentionally 17 instead of 16, because of how byond's tiles work and how not doing it will result in rounding errors like things getting put on the wrong turf. - -#define RETURN_PRECISE_POSITION(A) new /datum/position(A) -#define RETURN_PRECISE_POINT(A) new /datum/point(A) - -#define RETURN_POINT_VECTOR(ATOM, ANGLE, SPEED) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED)) -#define RETURN_POINT_VECTOR_INCREMENT(ATOM, ANGLE, SPEED, AMT) (new /datum/point/vector(ATOM, null, null, null, null, ANGLE, SPEED, AMT)) - /datum/position //For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess. var/x = 0 var/y = 0 @@ -224,4 +213,4 @@ var/needed_time = world.time - last_move last_process = world.time last_move = world.time - increment(needed_time / SSprojectiles.wait) \ No newline at end of file + increment(needed_time / SSprojectiles.wait) diff --git a/code/game/machinery/air_alarm.dm b/code/game/machinery/air_alarm.dm index 550e570d05..8bed01b6ab 100644 --- a/code/game/machinery/air_alarm.dm +++ b/code/game/machinery/air_alarm.dm @@ -682,7 +682,7 @@ var/list/selected = TLV["temperature"] var/max_temperature = min(selected[3] - T0C, MAX_TEMPERATURE) var/min_temperature = max(selected[2] - T0C, MIN_TEMPERATURE) - var/input_temperature = tgui_input_number(usr, "What temperature would you like the system to mantain? (Capped between [min_temperature] and [max_temperature]C)", "Thermostat Controls", target_temperature - T0C, max_temperature, min_temperature) + var/input_temperature = tgui_input_number(usr, "What temperature would you like the system to mantain? (Capped between [min_temperature] and [max_temperature]C)", "Thermostat Controls", target_temperature - T0C, max_temperature, min_temperature, round_value = FALSE) if(isnum(input_temperature)) if(input_temperature > max_temperature || input_temperature < min_temperature) to_chat(usr, "Temperature must be between [min_temperature]C and [max_temperature]C") @@ -736,7 +736,7 @@ var/env = params["env"] var/name = params["var"] - var/value = tgui_input_number(usr, "New [name] for [env]:", name, TLV[env][name]) + var/value = tgui_input_number(usr, "New [name] for [env]:", name, TLV[env][name], round_value = FALSE) if(!isnull(value) && !..()) if(value < 0) TLV[env][name] = -1 diff --git a/code/game/machinery/airconditioner_vr.dm b/code/game/machinery/airconditioner_vr.dm index 60104c83c8..9ee2ccca29 100644 --- a/code/game/machinery/airconditioner_vr.dm +++ b/code/game/machinery/airconditioner_vr.dm @@ -47,7 +47,7 @@ turn_off() return if(istype(I, /obj/item/device/multitool)) - var/new_temp = tgui_input_number(usr, "Input a new target temperature, in degrees C.","Target Temperature", 20) + var/new_temp = tgui_input_number(usr, "Input a new target temperature, in degrees C.","Target Temperature", convert_k2c(target_temp), round_value = FALSE) if(!Adjacent(user) || user.incapacitated()) return new_temp = convert_c2k(new_temp) @@ -157,4 +157,4 @@ #undef MODE_IDLE #undef MODE_HEATING -#undef MODE_COOLING \ No newline at end of file +#undef MODE_COOLING diff --git a/code/game/machinery/biogenerator.dm b/code/game/machinery/biogenerator.dm index 62a2a154a5..763564fcf9 100644 --- a/code/game/machinery/biogenerator.dm +++ b/code/game/machinery/biogenerator.dm @@ -305,3 +305,6 @@ build_eff = man_rating eat_eff = bin_rating + +#undef BIOGEN_ITEM +#undef BIOGEN_REAGENT diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index b8780d1e5e..c1121eb16e 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -1,4 +1,3 @@ - #define FIREDOOR_MAX_PRESSURE_DIFF 25 // kPa #define FIREDOOR_MAX_TEMP 50 // °C #define FIREDOOR_MIN_TEMP 0 @@ -519,3 +518,11 @@ icon = 'icons/obj/doors/DoorHazardGlass.dmi' icon_state = "door_open" glass = 1 + +#undef FIREDOOR_MAX_PRESSURE_DIFF +#undef FIREDOOR_MAX_TEMP +#undef FIREDOOR_MIN_TEMP + +#undef FIREDOOR_ALERT_HOT +#undef FIREDOOR_ALERT_COLD +// Not used #undef FIREDOOR_ALERT_LOWPRESS diff --git a/code/game/machinery/gear_dispenser.dm b/code/game/machinery/gear_dispenser.dm index 315b56e847..e410ecc816 100644 --- a/code/game/machinery/gear_dispenser.dm +++ b/code/game/machinery/gear_dispenser.dm @@ -21,8 +21,8 @@ var/list/dispenser_presets = list() if(LAZYLEN(req_one_access)) var/accesses = user.GetAccess() return has_access(null, req_one_access, accesses) - - return 1 + + return 1 /datum/gear_disp/proc/spawn_gear(var/turf/T, var/mob/living/carbon/human/user) var/list/spawned = list() @@ -40,7 +40,7 @@ var/list/dispenser_presets = list() /datum/gear_disp/custom/allowed(var/mob/living/carbon/human/user) if(ckey_allowed && user.ckey != ckey_allowed) return 0 - + if(character_allowed && user.real_name != character_allowed) return 0 @@ -81,7 +81,7 @@ var/list/dispenser_presets = list() voidsuit = new voidsuit_type(T) spawned += voidsuit // We only add the voidsuit so the game doesn't try to put the tank/helmet/boots etc into their hands - + // If we're supposed to make a helmet if(voidhelmet_type) // The coder may not have realized this type spawns its own helmet @@ -98,7 +98,7 @@ var/list/dispenser_presets = list() else magboots = new magboots_type(voidsuit) voidsuit.boots = magboots - + if(refit) voidsuit.refit_for_species(user.species?.get_bodytype()) // does helmet and boots if they're attached @@ -112,7 +112,7 @@ var/list/dispenser_presets = list() else if(user.species?.breath_type) if(voidsuit.tank) error("[src] created a voidsuit [voidsuit] and wants to add a tank but it already has one") - else + else //Create a tank (if such a thing exists for this species) var/tanktext = "/obj/item/weapon/tank/" + "[user.species?.breath_type]" var/obj/item/weapon/tank/tankpath = text2path(tanktext) @@ -135,7 +135,7 @@ var/list/dispenser_presets = list() /datum/gear_disp/voidsuit/custom/allowed(var/mob/living/carbon/human/user) if(ckey_allowed && user.ckey != ckey_allowed) return 0 - + if(character_allowed && user.real_name != character_allowed) return 0 @@ -172,7 +172,7 @@ var/list/dispenser_presets = list() if(one_setting) one_setting = new one_setting dispenses = real_gear_list - + /obj/machinery/gear_dispenser/attack_hand(var/mob/living/carbon/human/user) if(!can_use(user)) @@ -180,18 +180,18 @@ var/list/dispenser_presets = list() dispenser_flags |= GD_BUSY if(!(dispenser_flags & GD_ONEITEM)) var/list/gear_list = get_gear_list(user) - + if(!LAZYLEN(gear_list)) to_chat(user, "\The [src] doesn't have anything to dispense for you!") dispenser_flags &= ~GD_BUSY return - + var/choice = tgui_input_list(usr, "Select equipment to dispense.", "Equipment Dispenser", gear_list) - + if(!choice) dispenser_flags &= ~GD_BUSY return - + dispense(gear_list[choice],user) else dispense(one_setting,user) @@ -223,7 +223,7 @@ var/list/dispenser_presets = list() else audible_message("!'^&YouVE alreaDY pIC&$!Ked UP yOU%r Ge^!ar.") playsound(src, 'sound/machines/buzz-sigh.ogg', 100, 0) - return 1 + return 1 // And finally if(allowed(user)) return 1 @@ -261,7 +261,7 @@ var/list/dispenser_presets = list() var/turf/T = get_turf(src) if(!(S && T)) // in case we got destroyed while we slept return 1 - + S.spawn_gear(T, user) if(emagged) @@ -323,15 +323,15 @@ var/list/dispenser_presets = list() /obj/machinery/gear_dispenser/suit_fancy/update_icon() cut_overlays() - + if(special_frame) add_overlay(special_frame) - + if(needs_power && inoperable()) add_overlay("nopower") else add_overlay("light1") - + if(held_gear_disp) add_overlay("fullsuit") if(operable()) @@ -686,7 +686,7 @@ var/list/dispenser_presets = list() "gearlist": ["/obj/random/trash"] } ]"} - + /** * Needs to be valid json with keys of: * "menuoption" = string for the name @@ -696,12 +696,12 @@ var/list/dispenser_presets = list() var/input = tgui_input_text(usr, "Paste new gear pack JSON below. See example/code comments.", "Admin-load Dispenser", example, multiline = TRUE) if(!input) return - + var/list/parsed = json_decode(input) - + if(!islist(parsed)) return - + var/list/running = list() for(var/entry in parsed) if(!islist(entry)) @@ -710,7 +710,7 @@ var/list/dispenser_presets = list() var/option_name = this_entry["menuoption"] var/list/types = this_entry["gearlist"] var/list/access = this_entry["req_one_access"] - + if(!option_name || !islist(types)) continue @@ -721,15 +721,15 @@ var/list/dispenser_presets = list() var/tnew = text2path(t) if(ispath(tnew)) types += tnew - + var/datum/gear_disp/G = new() - + G.name = option_name G.to_spawn = types - + if(LAZYLEN(access)) G.req_one_access = access - + running[option_name] = G to_chat(usr, "[src] added [running.len] entries") dispenses = running @@ -945,4 +945,10 @@ var/list/dispenser_presets = list() /datum/gear_disp/adventure_box/armor, /datum/gear_disp/adventure_box/light, /datum/gear_disp/adventure_box/weapon - ) \ No newline at end of file + ) + +#undef GD_BUSY +#undef GD_ONEITEM +#undef GD_NOGREED +#undef GD_UNLIMITED +#undef GD_UNIQUE diff --git a/code/game/objects/effects/decals/Cleanable/tracks.dm b/code/game/objects/effects/decals/Cleanable/tracks.dm index cc24fd13e6..8c8344df2c 100644 --- a/code/game/objects/effects/decals/Cleanable/tracks.dm +++ b/code/game/objects/effects/decals/Cleanable/tracks.dm @@ -201,4 +201,6 @@ var/global/list/image/fluidtrack_cache=list() going_state = "" gender = PLURAL random_icon_states = null - amount = 0 \ No newline at end of file + amount = 0 + +#undef TRACKS_CRUSTIFY_TIME diff --git a/code/game/objects/items/devices/communicator/communicator.dm b/code/game/objects/items/devices/communicator/communicator.dm index 7eafc0fe05..d4fb3ff04a 100644 --- a/code/game/objects/items/devices/communicator/communicator.dm +++ b/code/game/objects/items/devices/communicator/communicator.dm @@ -376,3 +376,13 @@ var/global/list/obj/item/device/communicator/all_communicators = list() return icon_state = initial(icon_state) + +#undef HOMETAB +#undef PHONTAB +#undef CONTTAB +#undef MESSTAB +#undef NEWSTAB +#undef NOTETAB +#undef WTHRTAB +#undef MANITAB +#undef SETTTAB diff --git a/code/game/objects/items/weapons/joke.dm b/code/game/objects/items/weapons/joke.dm new file mode 100644 index 0000000000..008e3ec720 --- /dev/null +++ b/code/game/objects/items/weapons/joke.dm @@ -0,0 +1,19 @@ +/obj/item/weapon/squishhammer + name = "The Short Stacker" + desc = "Wield the power of this weapon with responsibility (God knows you won't)." + icon = 'icons/obj/items.dmi' + icon_state = "toyhammer" + attack_verb = list("stacked") + force = 0 + throwforce = 0 + +// Attack mob +/obj/item/weapon/squishhammer/attack(mob/M as mob, mob/user as mob) + var/is_squished = M.tf_scale_x || M.tf_scale_y + playsound(src, 'sound/items/hooh.ogg', 50, 1) + if(!is_squished) + M.SetTransform(null, (M.size_multiplier * 1.2), (M.size_multiplier * 0.5)) + else + M.ClearTransform() + M.update_transform() + return ..() \ No newline at end of file diff --git a/code/game/turfs/unsimulated/planetary_vr.dm b/code/game/turfs/unsimulated/planetary_vr.dm index b82342ea0a..144379a6f6 100644 --- a/code/game/turfs/unsimulated/planetary_vr.dm +++ b/code/game/turfs/unsimulated/planetary_vr.dm @@ -64,4 +64,4 @@ //other set - for map building /turf/unsimulated/wall2/planetary/virgo3b_better - icon_state = "riveted2" \ No newline at end of file + icon_state = "riveted2" diff --git a/code/global.dm b/code/global.dm index d156f96685..5a4ea32019 100644 --- a/code/global.dm +++ b/code/global.dm @@ -1,8 +1,3 @@ -//#define TESTING -#if DM_VERSION < 512 -#error This compiler is out of date Please update to at least BYOND 512. -#endif - // Items that ask to be called every cycle. var/global/datum/datacore/data_core = null var/global/list/machines = list() // ALL Machines, wether processing or not. diff --git a/code/modules/admin/admin_verb_lists_vr.dm b/code/modules/admin/admin_verb_lists_vr.dm index 4d6e351e1e..e903145cb3 100644 --- a/code/modules/admin/admin_verb_lists_vr.dm +++ b/code/modules/admin/admin_verb_lists_vr.dm @@ -175,7 +175,8 @@ var/list/admin_verbs_fun = list( /client/proc/narrate_mob, //VOREStation Add /client/proc/narrate_mob_args, //VOREStation Add /client/proc/getPlayerStatus, //VORESTation Add - /client/proc/manage_event_triggers + /client/proc/manage_event_triggers, + /client/proc/fake_pdaconvos ) @@ -518,6 +519,7 @@ var/list/admin_verbs_event_manager = list( /client/proc/check_ai_laws, //shows AI and borg laws, /client/proc/rename_silicon, //properly renames silicons, /client/proc/manage_silicon_laws, // Allows viewing and editing silicon laws. , + /client/proc/modify_robot, /client/proc/check_antagonists, /client/proc/admin_memo, //admin memo system. show/delete/write. +SERVER needed to delete admin memos of others, /client/proc/dsay, //talk in deadchat using our ckey/fakekey, diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2.dm b/code/modules/admin/verbs/SDQL2/SDQL_2.dm index 14b1bf7932..e04b578c95 100644 --- a/code/modules/admin/verbs/SDQL2/SDQL_2.dm +++ b/code/modules/admin/verbs/SDQL2/SDQL_2.dm @@ -1123,3 +1123,29 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null /obj/effect/statclick/SDQL2_VV_all/Click() usr.client.debug_variables(GLOB.sdql2_queries) + +#undef SDQL_qdel_datum + +#undef SDQL2_STATE_ERROR +#undef SDQL2_STATE_IDLE +#undef SDQL2_STATE_PRESEARCH +#undef SDQL2_STATE_SEARCHING +#undef SDQL2_STATE_EXECUTING +#undef SDQL2_STATE_SWITCHING +#undef SDQL2_STATE_HALTING + +// 2 undefs missing here still because of SDQL_2_parser + +#undef SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS +#undef SDQL2_OPTION_BLOCKING_CALLS +#undef SDQL2_OPTION_HIGH_PRIORITY +#undef SDQL2_OPTION_DO_NOT_AUTOGC + +#undef SDQL2_OPTIONS_DEFAULT + +#undef SDQL2_IS_RUNNING +#undef SDQL2_HALT_CHECK + +#undef SDQL2_TICK_CHECK + +#undef SDQL2_STAGE_SWITCH_CHECK diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm index dceff6ab46..8a0cc6c306 100644 --- a/code/modules/admin/verbs/adminhelp.dm +++ b/code/modules/admin/verbs/adminhelp.dm @@ -175,13 +175,13 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) MessageNoRecipient(parsed_message) send2adminchat() //VOREStation Add //show it to the person adminhelping too - to_chat(C, "PM to-Admins: [name]") + to_chat(C, "PM to-Admins: [name]") //send it to irc if nobody is on and tell us how many were on var/admin_number_present = send2irc_adminless_only(initiator_ckey, name) log_admin("Ticket #[id]: [key_name(initiator)]: [name] - heard by [admin_number_present] non-AFK admins who have +BAN.") if(admin_number_present <= 0) - to_chat(C, "No active admins are online, your adminhelp was sent to the admin discord.") //VOREStation Edit + to_chat(C, "No active admins are online, your adminhelp was sent to the admin discord.") //VOREStation Edit // Also send it to discord since that's the hip cool thing now. SSwebhooks.send( @@ -241,7 +241,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) //won't bug irc /datum/admin_help/proc/MessageNoRecipient(msg) var/ref_src = "\ref[src]" - var/chat_msg = "Ticket [TicketHref("#[id]", ref_src)]: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]: [msg]" + var/chat_msg = "Ticket [TicketHref("#[id]", ref_src)]: [LinkedReplyName(ref_src)] [FullMonty(ref_src)]: [msg]" AddInteraction("[LinkedReplyName(ref_src)]: [msg]") //send this msg to all admins @@ -612,7 +612,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new) if(tgui_alert(usr, "You already have a ticket open. Is this for the same issue?","Duplicate?",list("Yes","No")) != "No") if(current_ticket) current_ticket.MessageNoRecipient(msg) - to_chat(usr, "PM to-Admins: [msg]") + to_chat(usr, "PM to-Admins: [msg]") return else to_chat(usr, "Ticket not found, creating new one...") diff --git a/code/modules/admin/verbs/modify_robot.dm b/code/modules/admin/verbs/modify_robot.dm index 567a0d2d18..e90e06bd27 100644 --- a/code/modules/admin/verbs/modify_robot.dm +++ b/code/modules/admin/verbs/modify_robot.dm @@ -1,9 +1,9 @@ //Allows to add and remove modules from borgs -/client/proc/modify_robot(var/mob/living/silicon/robot/target in player_list) +/client/proc/modify_robot(var/mob/living/silicon/robot/target in silicon_mob_list) set name = "Modify Robot Module" set desc = "Allows to add or remove modules to/from robots." set category = "Admin" - if(!check_rights(R_ADMIN|R_FUN|R_VAREDIT)) + if(!check_rights(R_ADMIN|R_FUN|R_VAREDIT|R_EVENT)) return if(!istype(target) || !target.module) @@ -12,7 +12,8 @@ if(!target.module.modules) return - var/list/modification_options = list(MODIFIY_ROBOT_MODULE_ADD,MODIFIY_ROBOT_MODULE_REMOVE, MODIFIY_ROBOT_APPLY_UPGRADE, MODIFIY_ROBOT_SUPP_ADD, MODIFIY_ROBOT_SUPP_REMOVE, MODIFIY_ROBOT_RADIOC_ADD, MODIFIY_ROBOT_RADIOC_REMOVE, MODIFIY_ROBOT_COMP_ADD, MODIFIY_ROBOT_COMP_REMOVE, MODIFIY_ROBOT_RESET_MODULE) + var/list/modification_options = list(MODIFIY_ROBOT_MODULE_ADD,MODIFIY_ROBOT_MODULE_REMOVE, MODIFIY_ROBOT_APPLY_UPGRADE, MODIFIY_ROBOT_SUPP_ADD, MODIFIY_ROBOT_SUPP_REMOVE, MODIFIY_ROBOT_RADIOC_ADD, MODIFIY_ROBOT_RADIOC_REMOVE, + MODIFIY_ROBOT_COMP_ADD, MODIFIY_ROBOT_COMP_REMOVE, MODIFIY_ROBOT_SWAP_MODULE, MODIFIY_ROBOT_RESET_MODULE, MODIFIY_ROBOT_TOGGLE_ERT, MODIFIY_ROBOT_TOGGLE_CENT_ACCESS) while(TRUE) var/modification_choice = tgui_input_list(usr, "Select if you want to add or remove a module to/from [target]","Choice", modification_options) @@ -274,8 +275,41 @@ if(selected_component == "power cell") target.cell = null to_chat(usr, "You removed \"[C]\" from [target]") + if(MODIFIY_ROBOT_SWAP_MODULE) + var/selected_module = tgui_input_list(usr, "Which Module would you like to use?", "Module", robot_modules) + if(!selected_module || selected_module == "Cancel") + continue + if(!(selected_module in robot_modules)) + continue + target.uneq_all() + target.hud_used.update_robot_modules_display(TRUE) + target.modtype = initial(target.modtype) + target.module.Reset(target) + target.module.Destroy() + target.modtype = selected_module + var/module_type = robot_modules[selected_module] + target.transform_with_anim() + new module_type(target) + target.hands.icon_state = target.get_hud_module_icon() + target.hud_used.update_robot_modules_display() if(MODIFIY_ROBOT_RESET_MODULE) if(tgui_alert(usr, "Are you sure that you want to reset the entire module?","Confirm",list("Yes","No"))=="No") continue - target.module_reset() + target.module_reset(FALSE) to_chat(usr, "You resetted [target]'s module selection.") + if(MODIFIY_ROBOT_TOGGLE_ERT) + target.crisis_override = !target.crisis_override + to_chat(usr, "You [target.crisis_override? "enabled":"disabled"] [target]'s combat module overwrite.") + if(tgui_alert(usr, "Do you want to reset the module as well to allow selection?","Confirm",list("Yes","No"))=="No") + continue + target.module_reset(FALSE) + if(MODIFIY_ROBOT_TOGGLE_CENT_ACCESS) + for(var/obj in target.contents) + if(istype(obj, /obj/item/weapon/card/id/synthetic)) + var/obj/item/weapon/card/id/synthetic/card = obj + if(access_cent_specops in card.access) + card.access -= get_all_centcom_access() + to_chat(usr, "You revoke central access from [target].") + else + card.access += get_all_centcom_access() + to_chat(usr, "You grant central access to [target].") diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 64987e38c1..19f75f850e 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -100,7 +100,7 @@ to_chat(M, "You hear a voice in your head... [msg]") log_admin("SubtlePM: [key_name(usr)] -> [key_name(M)] : [msg]") - msg = " SubtleMessage: [key_name_admin(usr)] -> [key_name_admin(M)] : [msg]" + msg = " SubtleMessage: [key_name_admin(usr)] -> [key_name_admin(M)] : [msg]" message_admins(msg) admin_ticket_log(M, msg) feedback_add_details("admin_verb","SMS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! @@ -148,7 +148,7 @@ to_chat(M, msg) log_admin("DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]") - msg = " DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]
" + msg = " DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]
" message_admins(msg) admin_ticket_log(M, msg) feedback_add_details("admin_verb","DIRN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/admin/verbs/randomverbs_vr.dm b/code/modules/admin/verbs/randomverbs_vr.dm index 6a1e90aed1..d4836faf54 100644 --- a/code/modules/admin/verbs/randomverbs_vr.dm +++ b/code/modules/admin/verbs/randomverbs_vr.dm @@ -63,6 +63,8 @@ new_mob.key = picked_client.key //Finally put them in the mob if(organs) new_mob.copy_from_prefs_vr() + if(LAZYLEN(new_mob.vore_organs)) + new_mob.vore_selected = new_mob.vore_organs[1] log_admin("[key_name_admin(src)] has spawned [new_mob.key] as mob [new_mob.type].") message_admins("[key_name_admin(src)] has spawned [new_mob.key] as mob [new_mob.type].", 1) diff --git a/code/modules/admin/view_variables/helpers.dm b/code/modules/admin/view_variables/helpers.dm index a678271e88..9ee161b0dc 100644 --- a/code/modules/admin/view_variables/helpers.dm +++ b/code/modules/admin/view_variables/helpers.dm @@ -86,6 +86,11 @@ "} +/obj/item/device/pda/get_view_variables_options() + return ..() + {" + + "} + /datum/proc/get_variables() . = vars - VV_hidden() if(!usr || !check_rights(R_ADMIN|R_DEBUG, FALSE)) diff --git a/code/modules/admin/view_variables/topic.dm b/code/modules/admin/view_variables/topic.dm index 8a3f8811c2..09e15f7162 100644 --- a/code/modules/admin/view_variables/topic.dm +++ b/code/modules/admin/view_variables/topic.dm @@ -274,6 +274,15 @@ return log_admin("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) ") message_admins("[key_name(usr)] deleted all objects of type or subtype of [O_type] ([i] objects deleted) ") + else if(href_list["fakepdapropconvo"]) + if(!check_rights(R_FUN)) return + + var/obj/item/device/pda/P = locate(href_list["fakepdapropconvo"]) + if(!istype(P)) + to_chat(usr, span_warning("This can only be done to instances of type /pda")) + return + + P.createPropFakeConversation_admin(usr) else if(href_list["rotatedatum"]) if(!check_rights(0)) return diff --git a/code/modules/catalogue/cataloguer.dm b/code/modules/catalogue/cataloguer.dm index 36d78ff7cf..75a9acf6da 100644 --- a/code/modules/catalogue/cataloguer.dm +++ b/code/modules/catalogue/cataloguer.dm @@ -20,7 +20,7 @@ GLOBAL_LIST_EMPTY(all_cataloguers) Scanning something requires remaining within a certain radius of the object for a specific period of time, until the \ scan is finished. If the scan is interrupted, it can be resumed from where it was left off, if the same thing is \ scanned again." - icon = 'icons/obj/device.dmi' + icon = 'icons/obj/device_vr.dmi' icon_state = "cataloguer" w_class = ITEMSIZE_NORMAL origin_tech = list(TECH_MATERIAL = 2, TECH_DATA = 3, TECH_MAGNET = 3) @@ -28,7 +28,7 @@ GLOBAL_LIST_EMPTY(all_cataloguers) slot_flags = SLOT_BELT var/points_stored = 0 // Amount of 'exploration points' this device holds. var/scan_range = 3 // How many tiles away it can scan. Changing this also changes the box size. - var/credit_sharing_range = 14 // If another person is within this radius, they will also be credited with a successful scan. + var/credit_sharing_range = 280 // If another person is within this radius, they will also be credited with a successful scan. Original was 14 var/datum/category_item/catalogue/displayed_data = null // Used for viewing a piece of data in the UI. var/busy = FALSE // Set to true when scanning, to stop multiple scans. var/debug = FALSE // If true, can view all catalogue data defined, regardless of unlock status. @@ -37,6 +37,7 @@ GLOBAL_LIST_EMPTY(all_cataloguers) /obj/item/device/cataloguer/advanced name = "advanced cataloguer" + icon = 'icons/obj/device.dmi' icon_state = "adv_cataloguer" desc = "A hand-held device, used for compiling information about an object by scanning it. This one is an upgraded model, \ with a scanner that both can scan from farther away, and with less time." @@ -49,6 +50,7 @@ GLOBAL_LIST_EMPTY(all_cataloguers) desc = "A hand-held cataloguer device that appears to be plated with gold. For some reason, it \ just seems to already know everything about narrowly defined pieces of knowledge one would find \ from nearby, perhaps due to being colored gold. Truly a epistemological mystery." + icon = 'icons/obj/device.dmi' icon_state = "debug_cataloguer" toolspeed = 0.1 scan_range = 7 @@ -317,3 +319,65 @@ GLOBAL_LIST_EMPTY(all_cataloguers) to_chat(user, "\The [src] has no points available.") busy = FALSE return ..() + +/obj/item/device/cataloguer/compact + name = "compact cataloguer" + desc = "A compact hand-held device, used for compiling information about an object by scanning it. \ + Alt+click to highlight scannable objects around you." + icon = 'icons/obj/device_vr.dmi' + icon_state = "compact" + action_button_name = "Toggle Cataloguer" + var/deployed = TRUE + scan_range = 1 + toolspeed = 1.2 + +/obj/item/device/cataloguer/compact/pathfinder + name = "pathfinder's cataloguer" + desc = "A compact hand-held device, used for compiling information about an object by scanning it. \ + Alt+click to highlight scannable objects around you." + icon = 'icons/obj/device_vr.dmi' + icon_state = "pathcat" + scan_range = 3 + toolspeed = 1 + +/obj/item/device/cataloguer/compact/update_icon() + if(busy) + icon_state = "[initial(icon_state)]_s" + else + icon_state = initial(icon_state) + +/obj/item/device/cataloguer/compact/ui_action_click() + toggle() + +/obj/item/device/cataloguer/compact/verb/toggle() + set name = "Toggle Cataloguer" + set category = "Object" + + if(busy) + to_chat(usr, span("warning", "\The [src] is currently scanning something.")) + return + deployed = !(deployed) + if(deployed) + w_class = ITEMSIZE_NORMAL + icon_state = "[initial(icon_state)]" + to_chat(usr, span("notice", "You flick open \the [src].")) + else + w_class = ITEMSIZE_SMALL + icon_state = "[initial(icon_state)]_closed" + to_chat(usr, span("notice", "You close \the [src].")) + + if (ismob(usr)) + var/mob/M = usr + M.update_action_buttons() + +/obj/item/device/cataloguer/compact/afterattack(atom/target, mob/user, proximity_flag) + if(!deployed) + to_chat(user, span("warning", "\The [src] is closed.")) + return + return ..() + +/obj/item/device/cataloguer/compact/pulse_scan(mob/user) + if(!deployed) + to_chat(user, span("warning", "\The [src] is closed.")) + return + return ..() diff --git a/code/modules/catalogue/cataloguer_vr.dm b/code/modules/catalogue/cataloguer_vr.dm deleted file mode 100644 index 6759cca651..0000000000 --- a/code/modules/catalogue/cataloguer_vr.dm +++ /dev/null @@ -1,70 +0,0 @@ -/obj/item/device/cataloguer - credit_sharing_range = 280 - -/obj/item/device/cataloguer/compact - name = "compact cataloguer" - desc = "A compact hand-held device, used for compiling information about an object by scanning it. \ - Alt+click to highlight scannable objects around you." - icon = 'icons/obj/device_vr.dmi' - icon_state = "compact" - action_button_name = "Toggle Cataloguer" - var/deployed = TRUE - scan_range = 1 - toolspeed = 1.2 - -/obj/item/device/cataloguer/compact/update_icon() - if(busy) - icon_state = "[initial(icon_state)]_s" - else - icon_state = initial(icon_state) - -/obj/item/device/cataloguer/compact/ui_action_click() - toggle() - -/obj/item/device/cataloguer/compact/verb/toggle() - set name = "Toggle Cataloguer" - set category = "Object" - - if(busy) - to_chat(usr, span("warning", "\The [src] is currently scanning something.")) - return - deployed = !(deployed) - if(deployed) - w_class = ITEMSIZE_NORMAL - icon_state = "[initial(icon_state)]" - to_chat(usr, span("notice", "You flick open \the [src].")) - else - w_class = ITEMSIZE_SMALL - icon_state = "[initial(icon_state)]_closed" - to_chat(usr, span("notice", "You close \the [src].")) - - if (ismob(usr)) - var/mob/M = usr - M.update_action_buttons() - -/obj/item/device/cataloguer/compact/afterattack(atom/target, mob/user, proximity_flag) - if(!deployed) - to_chat(user, span("warning", "\The [src] is closed.")) - return - return ..() - -/obj/item/device/cataloguer/compact/pulse_scan(mob/user) - if(!deployed) - to_chat(user, span("warning", "\The [src] is closed.")) - return - return ..() - -/obj/item/device/cataloguer/compact/pathfinder - name = "pathfinder's cataloguer" - desc = "A compact hand-held device, used for compiling information about an object by scanning it. \ - Alt+click to highlight scannable objects around you." - icon = 'icons/obj/device_vr.dmi' - icon_state = "pathcat" - scan_range = 3 - toolspeed = 1 - -/obj/item/device/cataloguer - desc = "A hand-held device, used for compiling information about an object by scanning it. \ - Alt+click to highlight scannable objects around you." - icon = 'icons/obj/device_vr.dmi' - icon_state = "cataloguer" \ No newline at end of file diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm index 881d472149..3f1b5d81b4 100644 --- a/code/modules/client/client defines.dm +++ b/code/modules/client/client defines.dm @@ -47,6 +47,7 @@ var/last_message_count = 0 var/ircreplyamount = 0 var/entity_narrate_holder //Holds /datum/entity_narrate when using the relevant admin verbs. + var/fakeConversations //Holds fake PDA conversations for event set-up ///////// //OTHER// diff --git a/code/modules/client/preference_setup/global/01_ui.dm b/code/modules/client/preference_setup/global/01_ui.dm index 7f96fca3d0..221b89fcc2 100644 --- a/code/modules/client/preference_setup/global/01_ui.dm +++ b/code/modules/client/preference_setup/global/01_ui.dm @@ -14,7 +14,6 @@ S["tgui_fancy"] >> pref.tgui_fancy S["tgui_lock"] >> pref.tgui_lock S["tgui_input_mode"] >> pref.tgui_input_mode - S["tgui_input_lock"] >> pref.tgui_input_lock S["tgui_large_buttons"] >> pref.tgui_large_buttons S["tgui_swapped_buttons"] >> pref.tgui_swapped_buttons S["obfuscate_key"] >> pref.obfuscate_key @@ -34,7 +33,6 @@ S["tgui_fancy"] << pref.tgui_fancy S["tgui_lock"] << pref.tgui_lock S["tgui_input_mode"] << pref.tgui_input_mode - S["tgui_input_lock"] << pref.tgui_input_lock S["tgui_large_buttons"] << pref.tgui_large_buttons S["tgui_swapped_buttons"] << pref.tgui_swapped_buttons S["obfuscate_key"] << pref.obfuscate_key @@ -54,7 +52,6 @@ pref.tgui_fancy = sanitize_integer(pref.tgui_fancy, 0, 1, initial(pref.tgui_fancy)) pref.tgui_lock = sanitize_integer(pref.tgui_lock, 0, 1, initial(pref.tgui_lock)) pref.tgui_input_mode = sanitize_integer(pref.tgui_input_mode, 0, 1, initial(pref.tgui_input_mode)) - pref.tgui_input_lock = sanitize_integer(pref.tgui_input_lock, 0, 1, initial(pref.tgui_input_lock)) pref.tgui_large_buttons = sanitize_integer(pref.tgui_large_buttons, 0, 1, initial(pref.tgui_large_buttons)) pref.tgui_swapped_buttons = sanitize_integer(pref.tgui_swapped_buttons, 0, 1, initial(pref.tgui_swapped_buttons)) pref.obfuscate_key = sanitize_integer(pref.obfuscate_key, 0, 1, initial(pref.obfuscate_key)) @@ -74,7 +71,6 @@ . += "TGUI Window Mode: [(pref.tgui_fancy) ? "Fancy (default)" : "Compatible (slower)"]
" . += "TGUI Window Placement: [(pref.tgui_lock) ? "Primary Monitor" : "Free (default)"]
" . += "TGUI Input Framework: [(pref.tgui_input_mode) ? "Enabled" : "Disabled (default)"]
" - . += "TGUI Input Lock: [(pref.tgui_input_lock) ? "Enabled" : "Disabled (default)"]
" . += "TGUI Large Buttons: [(pref.tgui_large_buttons) ? "Enabled (default)" : "Disabled"]
" . += "TGUI Swapped Buttons: [(pref.tgui_swapped_buttons) ? "Enabled" : "Disabled (default)"]
" . += "Obfuscate Ckey: [(pref.obfuscate_key) ? "Enabled" : "Disabled (default)"]
" @@ -154,10 +150,6 @@ pref.tgui_input_mode = !pref.tgui_input_mode return TOPIC_REFRESH - else if(href_list["tgui_input_lock"]) - pref.tgui_input_lock = !pref.tgui_input_lock - return TOPIC_REFRESH - else if(href_list["tgui_large_buttons"]) pref.tgui_large_buttons = !pref.tgui_large_buttons return TOPIC_REFRESH diff --git a/code/modules/client/preference_setup/loadout/loadout_accessories_vr.dm b/code/modules/client/preference_setup/loadout/loadout_accessories_vr.dm index 3727a76c13..b33965dcd1 100644 --- a/code/modules/client/preference_setup/loadout/loadout_accessories_vr.dm +++ b/code/modules/client/preference_setup/loadout/loadout_accessories_vr.dm @@ -155,4 +155,28 @@ Talon pin /datum/gear/accessory/antediluvian/loin display_name = "antediluvian loincloth" - path = /obj/item/clothing/accessory/antediluvian/loincloth \ No newline at end of file + path = /obj/item/clothing/accessory/antediluvian/loincloth + +//Replikant accessories + +/datum/gear/accessory/sleekpatch + display_name = "sleek uniform patch" + path = /obj/item/clothing/accessory/sleekpatch + +/datum/gear/accessory/poncho/roles/cloak/custom/gestaltjacket + display_name = "sleek uniform jacket" + path = /obj/item/clothing/accessory/poncho/roles/cloak/custom/gestaltjacket + +/datum/gear/accessory/replika + display_name = "replikant vest selection" + path = /obj/item/clothing/accessory/replika + +/datum/gear/accessory/replika/New() + ..() + var/list/replika_vests = list( + "controller replikant chestplate" = /obj/item/clothing/accessory/replika/klbr, + "combat-engineer replikant chestplate" = /obj/item/clothing/accessory/replika/lstr, + "security-controller replikant chestplate" = /obj/item/clothing/accessory/replika/stcr, + "security-technician replikant chestplate" = /obj/item/clothing/accessory/replika/star + ) + gear_tweaks += new/datum/gear_tweak/path(replika_vests) diff --git a/code/modules/client/preference_setup/loadout/loadout_head.dm b/code/modules/client/preference_setup/loadout/loadout_head.dm index 1c8f475f5b..7fd90ffd9e 100644 --- a/code/modules/client/preference_setup/loadout/loadout_head.dm +++ b/code/modules/client/preference_setup/loadout/loadout_head.dm @@ -297,7 +297,8 @@ "engineering"=/obj/item/clothing/head/welding/engie, "fancy"=/obj/item/clothing/head/welding/fancy, "demonic"=/obj/item/clothing/head/welding/demon, - "knightly"=/obj/item/clothing/head/welding/knight + "knightly"=/obj/item/clothing/head/welding/knight, + "replikant"=/obj/item/clothing/head/welding/arar ) gear_tweaks += new/datum/gear_tweak/path(sortAssoc(selector_uniforms)) diff --git a/code/modules/client/preference_setup/loadout/loadout_head_vr.dm b/code/modules/client/preference_setup/loadout/loadout_head_vr.dm index 52d81e806d..48015f229e 100644 --- a/code/modules/client/preference_setup/loadout/loadout_head_vr.dm +++ b/code/modules/client/preference_setup/loadout/loadout_head_vr.dm @@ -92,3 +92,9 @@ Talon hats /datum/gear/head/tiny_tophat display_name = "tiny tophat" path = /obj/item/clothing/head/tinytophat + +//Replikant hat + +/datum/gear/head/eulrhat + display_name = "Sleek side cap" + path = /obj/item/clothing/head/eulrhat diff --git a/code/modules/client/preference_setup/loadout/loadout_uni_selector.dm b/code/modules/client/preference_setup/loadout/loadout_uni_selector.dm index 12eb8bfd21..2d5e732d50 100644 --- a/code/modules/client/preference_setup/loadout/loadout_uni_selector.dm +++ b/code/modules/client/preference_setup/loadout/loadout_uni_selector.dm @@ -593,3 +593,43 @@ "TG&C jumpskirt"=/obj/item/clothing/under/rank/neo_robo_skirt ) gear_tweaks += new/datum/gear_tweak/path(sortAssoc(selector_uniforms)) + +//Replikant & Signalis-themed human-wear + +/datum/gear/uniform/replikant_selector + display_name = "Replikant Uniform Selection" + description = "Several variants of bodysuit designed for Second Generation Biosynthetics." + path = /obj/item/clothing/under/replika/arar + sort_category = "Uniforms" + cost = 1 + +/datum/gear/uniform/replikant_selector/New() + ..() + var/list/selector_uniforms = list( + "Macaw"=/obj/item/clothing/under/replika/arar, + "Magpie"=/obj/item/clothing/under/replika/lstr, + "Falcon"=/obj/item/clothing/under/replika/fklr, + "Owl"=/obj/item/clothing/under/replika/eulr, + "Hummingbird"=/obj/item/clothing/under/replika/klbr, + "Stork/Starling"=/obj/item/clothing/under/replika/stcr, + "Eagle"=/obj/item/clothing/under/replika/adlr, + "Magpie, Alternate"=/obj/item/clothing/under/replika/lstr_alt + ) + gear_tweaks += new/datum/gear_tweak/path(sortAssoc(selector_uniforms)) + +/datum/gear/uniform/gestalt_selector + display_name = "Sleek Uniform Selection" + description = "Multiple variants of single-stripe pattern uniforms. Best worn under their accompanying jacket." + path = /obj/item/clothing/under/gestalt + sort_category = "Uniforms" + cost = 1 + +/datum/gear/uniform/gestalt_selector/New() + ..() + var/list/selector_uniforms = list( + "Sleek, standard"=/obj/item/clothing/under/gestalt/sleek, + "Sleek, skirt"=/obj/item/clothing/under/gestalt/sleek_skirt, + "Sleek, feminine"=/obj/item/clothing/under/gestalt/sleek_fem, + "Sleek, sleeveless"=/obj/item/clothing/under/gestalt/sleeveless + ) + gear_tweaks += new/datum/gear_tweak/path(sortAssoc(selector_uniforms)) diff --git a/code/modules/client/preference_setup/loadout/loadout_uniform.dm b/code/modules/client/preference_setup/loadout/loadout_uniform.dm index b6bad99996..f5c9aab68c 100644 --- a/code/modules/client/preference_setup/loadout/loadout_uniform.dm +++ b/code/modules/client/preference_setup/loadout/loadout_uniform.dm @@ -516,6 +516,10 @@ display_name = "high-waisted trousers" path = /obj/item/clothing/under/dress/hightrousers +/datum/gear/uniform/vampirehunter + display_name = "18th century outfit" + path = /obj/item/clothing/under/vampirehunter + /* * 80s */ diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 27f0a95c18..c6bdc7aabf 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -29,7 +29,6 @@ var/list/preferences_datums = list() var/tgui_fancy = TRUE var/tgui_lock = FALSE var/tgui_input_mode = FALSE // All the Input Boxes (Text,Number,List,Alert) - var/tgui_input_lock = FALSE var/tgui_large_buttons = TRUE var/tgui_swapped_buttons = FALSE var/obfuscate_key = FALSE diff --git a/code/modules/client/preferences_toggle_procs.dm b/code/modules/client/preferences_toggle_procs.dm index fb5f3d70c5..9e9f00c659 100644 --- a/code/modules/client/preferences_toggle_procs.dm +++ b/code/modules/client/preferences_toggle_procs.dm @@ -379,17 +379,6 @@ You will have to reload TGChat and/or reconnect to the server for these changes to take place. \ TGChat message persistence is not guaranteed if you change this again before the start of the next round.") -/client/verb/toggle_tgui_inputlock() - set name = "Toggle TGUI Input Lock" - set category = "Preferences" - set desc = "Toggles whether or not pressing the 'Enter' key in TGUI input sends the message or creates a new line." - - prefs.tgui_input_lock = !prefs.tgui_input_lock //There is no preference datum for tgui input lock, nor for any TGUI prefs. - SScharacter_setup.queue_preferences_save(prefs) - - to_chat(src, span_notice("You have toggled TGUI input lock: [prefs.tgui_input_lock ? "ON" : "OFF"] \n \ - This setting determines whether pressing enter on TGUI input sends the input, or creates a newline.")) - /client/verb/toggle_chat_timestamps() set name = "Toggle Chat Timestamps" set category = "Preferences" diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm index 724b77a9af..c2509fbb88 100644 --- a/code/modules/clothing/head/misc_special.dm +++ b/code/modules/clothing/head/misc_special.dm @@ -103,6 +103,22 @@ slot_r_hand_str = "engiewelding", ) +//Replikant Welding mask + +/obj/item/clothing/head/welding/arar + name = "replikant welding helmet" + desc = "A protective welding mask designed for repair-technician biosynthetic crew, the visor slits are particularly difficult to see out of." + icon = 'icons/inventory/head/item_vr.dmi' + icon_override = 'icons/inventory/head/mob_vr.dmi' + icon_state = "ararwelding" + item_state_slots = list( + SLOT_ID_LEFT_HAND = "ararwelding", + SLOT_ID_RIGHT_HAND = "ararwelding", + ) + + + + /* * Cakehat */ @@ -319,4 +335,4 @@ w_class = 2 body_parts_covered = HEAD attack_verb = list("warned", "cautioned", "smashed") - armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) \ No newline at end of file + armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) diff --git a/code/modules/clothing/head/misc_vr.dm b/code/modules/clothing/head/misc_vr.dm index 9b9a6a6ed3..b94c53fa2b 100644 --- a/code/modules/clothing/head/misc_vr.dm +++ b/code/modules/clothing/head/misc_vr.dm @@ -171,4 +171,13 @@ desc = "A tophat that is far too small to properly sit on someone's head!" icon = 'icons/inventory/head/item_vr.dmi' default_worn_icon = 'icons/inventory/head/mob_vr.dmi' - icon_state = "tiny_tophat" \ No newline at end of file + icon_state = "tiny_tophat" + +//Replikant Hat + +/obj/item/clothing/head/eulrhat + name = "sleek side cap" + desc = "A simple wedge cap with red accents, popular with biosynthetic personnel." + icon = 'icons/inventory/head/item_vr.dmi' + icon_override = 'icons/inventory/head/mob_vr.dmi' + icon_state = "eulrhat" diff --git a/code/modules/clothing/under/accessories/accessory_vr.dm b/code/modules/clothing/under/accessories/accessory_vr.dm index fec423c436..5a4a910e5e 100644 --- a/code/modules/clothing/under/accessories/accessory_vr.dm +++ b/code/modules/clothing/under/accessories/accessory_vr.dm @@ -493,7 +493,14 @@ /obj/item/clothing/accessory/collar/shock/bluespace/relaymove(var/mob/living/user,var/direction) return //For some reason equipping this item was triggering this proc, putting the wearer inside of the collars belly for some reason. -/obj/item/clothing/accessory/collar/shock/bluespace/attackby(var/obj/item/device/assembly/signaler/component, mob/user as mob) +/obj/item/clothing/accessory/collar/shock/bluespace/attackby(var/obj/item/component, mob/user as mob) + if (component.has_tool_quality(TOOL_WRENCH)) + to_chat(user, "You crack the bluespace crystal [src].") + var/turf/T = get_turf(src) + new /obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning(T) + user.drop_from_inventory(src) + qdel(src) + return if (!istype(component,/obj/item/device/assembly/signaler)) ..() return @@ -517,7 +524,14 @@ target_size = 1 on = 1 -/obj/item/clothing/accessory/collar/shock/bluespace/modified/attackby(var/obj/item/device/assembly/signaler/component, mob/user as mob) +/obj/item/clothing/accessory/collar/shock/bluespace/modified/attackby(var/obj/item/component, mob/user as mob) + if (component.has_tool_quality(TOOL_WRENCH)) + to_chat(user, "You crack the bluespace crystal [src], the attached signaler disconnects.") + var/turf/T = get_turf(src) + new /obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning(T) + user.drop_from_inventory(src) + qdel(src) + return if (!istype(component,/obj/item/device/assembly/signaler)) ..() return @@ -625,6 +639,141 @@ s.start() return +//bluespace collar malfunctioning (random size) + +/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning + name = "Bluespace collar" + desc = "A collar that can manipulate the size of the wearer, and can be modified when unequiped. It has a crack on the crystal." + icon_state = "collar_size_malf" + item_state = "collar_size" + overlay_state = "collar_size" + target_size = 1 + on = 1 + var/currently_shrinking = 0 + +/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning/attackby(var/obj/item/component, mob/user as mob) + if (!istype(component,/obj/item/device/assembly/signaler)) + ..() + return + to_chat(user, "The signaler doesn't respond to the connection attempt [src].") + return + +/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning/attack_self(mob/user as mob, flag1) + if(!istype(user, /mob/living/carbon/human)) + return + user.set_machine(src) + var/dat = {" + Frequency/Code for collar:
+ Frequency: + - + - [format_frequency(frequency)] + + + +
+ + Code: + - + - [code] + + + +
+ + Tag: + Set tag
+ + Size: + Input Disabled!
+
"} + user << browse(dat, "window=radio") + onclose(user, "radio") + return + +/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning/Topic(href, href_list) + if(usr.stat || usr.restrained()) + return + if(((istype(usr, /mob/living/carbon/human) && ((!( ticker ) || (ticker && ticker.mode != "monkey")) && usr.contents.Find(src))) || (usr.contents.Find(master) || (in_range(src, usr) && istype(loc, /turf))))) + usr.set_machine(src) + if(href_list["freq"]) + var/new_frequency = sanitize_frequency(frequency + text2num(href_list["freq"])) + set_frequency(new_frequency) + if(href_list["tag"]) + var/str = copytext(reject_bad_text(tgui_input_text(usr,"Tag text?","Set tag","",MAX_NAME_LEN)),1,MAX_NAME_LEN) + if(!str || !length(str)) + to_chat(usr,"[name]'s tag set to be blank.") + name = initial(name) + desc = initial(desc) + else + to_chat(usr,"You set the [name]'s tag to '[str]'.") + name = initial(name) + " ([str])" + desc = initial(desc) + " The tag says \"[str]\"." + else + if(href_list["code"]) + code += text2num(href_list["code"]) + code = round(code) + code = min(100, code) + code = max(1, code) + if(!( master )) + if(istype(loc, /mob)) + attack_self(loc) + else + for(var/mob/M in viewers(1, src)) + if(M.client) + attack_self(M) + else + if(istype(master.loc, /mob)) + attack_self(master.loc) + else + for(var/mob/M in viewers(1, master)) + if(M.client) + attack_self(M) + else + usr << browse(null, "window=radio") + return + return + +/obj/item/clothing/accessory/collar/shock/bluespace/malfunctioning/receive_signal(datum/signal/signal) + if(!signal) + return + target_size = (rand(25,200)) /100 + if(on) + var/mob/M = null + if(ismob(loc)) + M = loc + if(ismob(loc.loc)) + M = loc.loc // This is about as terse as I can make my solution to the whole 'collar won't work when attached as accessory' thing. + var/mob/living/carbon/human/H = M + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + if(!H.resizable) + H.visible_message("The space around [H] compresses for a moment but then nothing happens.","The space around you distorts but nothing happens to you.") + return + if (target_size < 0.25) + H.visible_message("The collar on [H] flickers, but fizzles out.","Your collar flickers, but is not powerful enough to shrink you that small.") + return + if(currently_shrinking == 0) + if(!(world.time - last_activated > 10 SECONDS)) + to_chat(M, "\The [src] flickers. It seems to be recharging.") + return + last_activated = world.time + original_size = H.size_multiplier + currently_shrinking = 1 + H.resize(target_size, ignore_prefs = FALSE) //In case someone else tries to put it on you. + H.visible_message("The space around [H] distorts as they change size!","The space around you distorts as you change size!") + log_admin("Admin [key_name(M)]'s size was altered by a bluespace collar.") + s.set_up(3, 1, M) + s.start() + else if(currently_shrinking == 1) + if(original_size == null) + H.visible_message("The space around [H] twists and turns for a moment but then nothing happens.","The space around you distorts but stay the same size.") + return + last_activated = world.time + H.resize(original_size, ignore_prefs = FALSE) + original_size = null + currently_shrinking = 0 + H.visible_message("The space around [H] distorts as they return to their original size!","The space around you distorts as you return to your original size!") + log_admin("Admin [key_name(M)]'s size was altered by a bluespace collar.") + to_chat(M, "\The [src] flickers. It is now recharging and will be ready again in ten seconds.") + s.set_up(3, 1, M) + s.start() + return + //Machete Holsters /obj/item/clothing/accessory/holster/machete name = "machete sheath" @@ -990,3 +1139,17 @@ desc = "A cut down jacket that looks like it's light enough to wear on top of some other clothes. This one's a sort of olive-drab kind of colour." icon_state = "cropjacket_drab" item_state = "cropjacket_drab" + +//Replikant patch & jacket + +/obj/item/clothing/accessory/sleekpatch + name = "sleek uniform patch" + desc = "A somewhat old-fashioned embroidered patch of Nanotrasen's logo." + icon_state = "sleekpatch" + item_state = "sleekpatch" + +/obj/item/clothing/accessory/poncho/roles/cloak/custom/gestaltjacket + name = "sleek uniform jacket" + desc = "Barely more than a pair of long stirrup sleeves joined by a turtleneck. Has decorative red accents." + icon_state = "gestaltjacket" + item_state = "gestaltjacket" diff --git a/code/modules/clothing/under/accessories/clothing.dm b/code/modules/clothing/under/accessories/clothing.dm index 503027b9b5..121cf61c5e 100644 --- a/code/modules/clothing/under/accessories/clothing.dm +++ b/code/modules/clothing/under/accessories/clothing.dm @@ -543,3 +543,39 @@ /obj/item/clothing/accessory/cowboy_vest/grey name = "grey cowboy vest" icon_state = "cowboyvest_grey" + +//Replikant Vests + +/obj/item/clothing/accessory/replika + name = "generic" + desc = "generic" + icon = 'icons/inventory/accessory/item.dmi' + icon_state = "klbr" + icon_override = 'icons/inventory/accessory/mob.dmi' + item_state_slots = list(SLOT_ID_RIGHT_HAND = "armor", SLOT_ID_LEFT_HAND = "armor") + allowed = list(/obj/item/weapon/gun,/obj/item/weapon/reagent_containers/spray/pepper,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/weapon/melee/baton,/obj/item/weapon/handcuffs,/obj/item/device/flashlight/maglight,/obj/item/clothing/head/helmet) + slot_flags = SLOT_OCLOTHING | SLOT_TIE + body_parts_covered = UPPER_TORSO|ARMS + siemens_coefficient = 0.9 + w_class = ITEMSIZE_NORMAL + slot = ACCESSORY_SLOT_OVER + +/obj/item/clothing/accessory/replika/klbr + name = "controller replikant chestplate" + desc = "A sloped titanium-composite chest plate fitted for use by 2nd generation biosynthetics. The right shoulder has been painted an imposing shade of red." + icon_state = "klbr" + +/obj/item/clothing/accessory/replika/lstr + name = "combat-engineer replikant chestplate" + desc = "A sloped titanium-composite chest plate fitted for use by 2nd generation biosynthetics. This plain-white version is a staple of biosynths assinged to combat-engineering duties." + icon_state = "lstr" + +/obj/item/clothing/accessory/replika/stcr + name = "security-controller replikant chestplate" + desc = "A sloped titanium-composite chest plate fitted for use by 2nd generation biosynthetics. This version sports multiple red adjustable straps and a lack of shoulder pads." + icon_state = "stcr" + +/obj/item/clothing/accessory/replika/star + name = "security-technician replikant chestplate" + desc = "A sloped titanium-composite chest plate with a matte black finish, fitted for use by 2nd generation biosynthetics. Comes with red adjustable straps." + icon_state = "star" diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index ddb57e8bbb..ea62da0c66 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -1216,6 +1216,11 @@ desc = "An orange cohesion suit with yellow hazard stripes intended to assist Prometheans in maintaining their form and prevent direct skin exposure." icon_state = "cohesionsuit_hazard" +/obj/item/clothing/under/vampirehunter + name = "18th century outfit" + desc = "A flashy, if rather stiff set of ancient-styled slacks and tabard. The unyielding nature of the clothes often make one walk stiffly, but with divine purpose." + icon_state = "belmont" + //Ranger uniforms //On-mob sprites go in icons\mob\uniform.dmi with the format "white_ranger_uniform_s" - with 'white' replaced with green, cyan, etc... of course! Note the _s - this is not optional. //Item sprites go in icons\obj\clothing\ranger.dmi with the format "white_ranger_uniform" diff --git a/code/modules/clothing/under/miscellaneous_vr.dm b/code/modules/clothing/under/miscellaneous_vr.dm index 5b89374acc..bbc7ee457a 100644 --- a/code/modules/clothing/under/miscellaneous_vr.dm +++ b/code/modules/clothing/under/miscellaneous_vr.dm @@ -616,3 +616,115 @@ desc = "A fancy gown for those who like to show leg. Perfect for recoloring!" default_worn_icon = 'icons/inventory/uniform/mob_vr.dmi' icon_state = "cswoopdress" + +//Replikant uniforms + +/obj/item/clothing/under/replika + name = "generic" + desc = "generic" + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon = 'icons/inventory/uniform/item_vr.dmi' + default_worn_icon = 'icons/inventory/uniform/mob_vr.dmi' + icon_state = "arar" + item_state = "arar" + rolled_sleeves = -1 + rolled_down = -1 + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS + +/obj/item/clothing/under/replika/arar + name = "repair-worker replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of the engineering variety. Comes with multiple interfacing ports, arm protectors, and a conspicuous lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "arar" + item_state = "arar" + + +/obj/item/clothing/under/replika/lstr + name = "land-survey replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of the exploration variety. Comes with several interfacing ports and a conspicuous lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "lstr" + item_state = "lstr" + +/obj/item/clothing/under/replika/fklr + name = "command replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of the command variety. Comes with interfacing ports, an air of formality, and a conspicuous lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "fklr" + item_state = "fklr" + +/obj/item/clothing/under/replika/eulr + name = "general-purpose replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of multipurpose variety. Comes with default interfacing ports and a conspicuous lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "eulr" + item_state = "eulr" + +/obj/item/clothing/under/replika/klbr + name = "controller replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of the controller variety. Comes with several interfacing ports and a conspicuous lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "klbr" + item_state = "klbr" + +/obj/item/clothing/under/replika/stcr + name = "security-technician replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of the security variety. Comes with multiple interfacing ports and a conspicuous lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "stcr" + item_state = "stcr" + +/obj/item/clothing/under/replika/adlr + name = "administration replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of the administrative variety. Comes with several interfacing ports and a conspicuous lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "adlr" + item_state = "adlr" + +/obj/item/clothing/under/replika/lstr_alt + name = "combat-engineer replikant bodysuit" + desc = "A skin-tight bodysuit designed for 2nd generation biosynthetics of the exploration variety. Comes with extra interfacing ports, white armpads, and a familiar lack of leg coverage." + description_fluff = "These purpose-made interfacing bodysuits are designed and produced by the Singheim Bureau of Biosynthetic Development for their long-running second generation of Biosynthetics, commonly known by the term Replikant. Although anyone could wear these, their overall cut and metallic ports along the spine make it rather uncomfortable to most." + icon_state = "lstr_alt" + item_state = "lstr_alt" + +//Signalis-themed human-wear + +/obj/item/clothing/under/gestalt + name = "generic" + desc = "generic" + icon = 'icons/inventory/uniform/item_vr.dmi' + default_worn_icon = 'icons/inventory/uniform/mob_vr.dmi' + icon_state = "gestalt_skirt" + item_state = "gestalt_skirt" + rolled_sleeves = -1 + rolled_down = -1 + body_parts_covered = UPPER_TORSO|LOWER_TORSO|ARMS|LEGS + +/obj/item/clothing/under/gestalt/sleek_skirt + name = "sleek crew skirt" + desc = "A tight-fitting black uniform with a narrow skirt and striking crimson trim." + icon_state = "gestalt_skirt" + item_state = "gestalt_skirt" + + +/obj/item/clothing/under/gestalt/sleek + name = "sleek crew uniform" + desc = "A tight-fitting black uniform with striking crimson trim." + icon_state = "gestalt" + item_state = "gestalt" + + +/obj/item/clothing/under/gestalt/sleek_fem + name = "sleek female crew uniform" + desc = "A tight-fitting black uniform with striking crimson trim." + icon_state = "gestalt_fem" + item_state = "gestalt_fem" + + +/obj/item/clothing/under/gestalt/sleeveless + name = "sleeveless sleek crew uniform" + desc = "A tight-fitting, sleeveless single-piece black uniform with striking crimson trim." + icon_state = "gestalt_sleeveless" + item_state = "gestalt_sleeveless" + body_parts_covered = UPPER_TORSO|LOWER_TORSO|LEGS diff --git a/code/modules/eventkit/gm_interfaces/fake_pda_conversations.dm b/code/modules/eventkit/gm_interfaces/fake_pda_conversations.dm new file mode 100644 index 0000000000..e6ff399c01 --- /dev/null +++ b/code/modules/eventkit/gm_interfaces/fake_pda_conversations.dm @@ -0,0 +1,95 @@ +/datum/eventkit/fake_pdaconvos + var/list/names = list() //Assoc list of refs in fakeRefs = name + var/list/fakeRefs = list() //Used to find elements in other lists and tracking conversations. MUST BE UNIQUE. + var/list/fakeJobs = list() //Assoc list of name in names = job + + +/client/proc/fake_pdaconvos() + set category = "EventKit" + set name = "Manage PDA identities" + set desc = "Creates fake identities for use in setting up PDA props" + + if(!check_rights(R_FUN)) + return + + var/choice = tgui_input_list(usr, "What do you wish to do?", "Options", + list("Add new identity", "Edit existing identity", "Delete existing identity", "Delete holder", "Cancel")) + + if(choice == "Delete holder") + qdel(fakeConversations) + fakeConversations = null + return + if(choice == "Cancel") + return + + if(!fakeConversations || !istype(fakeConversations, /datum/eventkit/fake_pdaconvos)) + fakeConversations = new /datum/eventkit/fake_pdaconvos + + var/datum/eventkit/fake_pdaconvos/FPC = fakeConversations + + if(choice == "Add new identity") + var/newRef = sanitize(tgui_input_text(usr, "Input unique reference. Duplicates are FORBIDDEN!. Players can't see this.\ + Used to uniquely identify conversations in PDAs", null)) + if(!newRef) return + FPC.fakeRefs.Add(newRef) + FPC.names[newRef] = sanitize(tgui_input_text(usr, "Input fake name",newRef)) + FPC.fakeJobs[newRef] = sanitize(tgui_input_text(usr, "Input fake assignment.",newRef)) + to_chat(usr, span_notice("You have created [newRef]. Current name: [FPC.names[newRef]]. Current assignment: [FPC.fakeJobs[newRef]]")) + return + + if(choice == "Edit existing identity") + var/ref = tgui_input_list(usr, "Pick which identity to edit (details are printed to chat)", "identities", FPC.fakeRefs) + to_chat(usr, span_notice("You are editing [ref]. Current name: [FPC.names[ref]]. Current assignment: [FPC.fakeJobs[ref]]")) + var/editChoice = tgui_alert(usr, "What do you wish to edit?", "Details", list("Name", "Job", "Cancel")) + if(editChoice == "Name") + FPC.names[ref] = sanitize(tgui_input_text(usr, "Input fake name", FPC.names[ref])) + to_chat(usr, span_notice("Current data for [ref] are : Current name: [FPC.names[ref]]. Current assignment: [FPC.fakeJobs[ref]]")) + if(editChoice == "Job") + FPC.fakeJobs[ref] = sanitize(tgui_input_text(usr, "Input fake name", FPC.fakeJobs[ref])) + to_chat(usr, span_notice("Current data for [ref] are : Current name: [FPC.names[ref]]. Current assignment: [FPC.fakeJobs[ref]]")) + return + if(choice == "Delete existing identity") + var/ref = tgui_input_list(usr, "Pick which identity to delete (details are printed to chat)", "identities", FPC.fakeRefs) + if(tgui_alert(usr, "You are deleting [ref]. Current name: [FPC.names[ref]]. Current assignment: [FPC.fakeJobs[ref]]", + "are you sure?", list("Yes", "No"))=="Yes") + FPC.fakeRefs =- ref + FPC.fakeJobs =- ref + FPC.names =- ref + return + + + +/* +Invoked by vv topic "fakepdapropconvo" in code\modules\admin\view_variables\topic.dm found in PDA vv dropdown. +*/ +/obj/item/device/pda/proc/createPropFakeConversation_admin(mob/M) + if(!M.client || !check_rights_for(M.client,R_FUN)) + return + + var/datum/eventkit/fake_pdaconvos/FPC = M.client.fakeConversations + + if(!FPC || !istype(FPC, /datum/eventkit/fake_pdaconvos)) + to_chat(M, span_warning("First you must create a new identity with Manage PDA identities in EventKit")) + return + + var/choice = tgui_alert(M,"Use TGUI or dialogue boxes?", "TGUI?", list("TGUI", "Dialogue", "Cancel")) + if(choice == "Cancel") return + + var/datum/data/pda/app/messenger/ourPDA = find_program(/datum/data/pda/app/messenger) + + if(choice == "Dialogue") + var/identity = tgui_input_list(M, "Pick which identity to use(details are printed to chat)", "identities", FPC.fakeRefs) + var/job = FPC.fakeJobs[identity] + var/name = FPC.names[identity] + to_chat(M, span_notice("You are using [identity]. Current name: [name]. Current assignment: [job]")) + var/safetyLimit = 0 + while(safetyLimit < 30) + safetyLimit += 1 + var/message = sanitize(tgui_input_text(M, "Input fake message. Leave empty to cancel. Can create up to 30 messages in a row",null), + MAX_MESSAGE_LEN) + if(!message) return + var/receipent = ((tgui_alert(M, "Received or Sent?", "Direction", list("Received", "Sent"))=="Sent") ? 1 : 0) + ourPDA.createFakeMessage(name,identity,job,receipent, message) + + if(choice == "TGUI") + to_chat(M, span_notice("Sorry, the TGUI functionality is not yet implemented - use Dialogue mode!")) diff --git a/code/modules/mob/language/language.dm b/code/modules/mob/language/language.dm index eaa075713e..d7a8d11481 100644 --- a/code/modules/mob/language/language.dm +++ b/code/modules/mob/language/language.dm @@ -154,7 +154,7 @@ /mob/proc/hear_broadcast(var/datum/language/language, var/mob/speaker, var/speaker_name, var/message) if((language in languages) && language.check_special_condition(src)) - var/msg = "[language.name], [speaker_name] [message]" + var/msg = span_hivemind("[language.name], [speaker_name] [message]") to_chat(src,msg) /mob/new_player/hear_broadcast(var/datum/language/language, var/mob/speaker, var/speaker_name, var/message) @@ -162,9 +162,9 @@ /mob/observer/dead/hear_broadcast(var/datum/language/language, var/mob/speaker, var/speaker_name, var/message) if(speaker.name == speaker_name || antagHUD) - to_chat(src, "[language.name], [speaker_name] ([ghost_follow_link(speaker, src)]) [message]") + to_chat(src, span_hivemind("[language.name], [speaker_name] ([ghost_follow_link(speaker, src)]) [message]")) else - to_chat(src, "[language.name], [speaker_name] [message]") + to_chat(src, span_hivemind("[language.name], [speaker_name] [message]")) /datum/language/proc/check_special_condition(var/mob/other) return 1 diff --git a/code/modules/mob/language/synthetic.dm b/code/modules/mob/language/synthetic.dm index 941dd84201..6a4ccd494d 100644 --- a/code/modules/mob/language/synthetic.dm +++ b/code/modules/mob/language/synthetic.dm @@ -20,12 +20,12 @@ message = encode_html_emphasis(message) - var/message_start = "[name], [speaker.name]" - var/message_body = "[speaker.say_quote(message)], \"[message]\"" + var/message_start = "[name], [speaker.name]" + var/message_body = "[speaker.say_quote(message)], \"[message]\"" for (var/mob/M in dead_mob_list) if(!istype(M,/mob/new_player) && !istype(M,/mob/living/carbon/brain)) //No meta-evesdropping - var/message_to_send = "[message_start] ([ghost_follow_link(speaker, M)]) [message_body]" + var/message_to_send = span_binary("[message_start] ([ghost_follow_link(speaker, M)]) [message_body]") if(M.check_mentioned(message) && M.is_preference_enabled(/datum/client_preference/check_mention)) message_to_send = "[message_to_send]" M.show_message(message_to_send, 2) @@ -34,11 +34,11 @@ if(drone_only && !istype(S,/mob/living/silicon/robot/drone)) continue else if(istype(S , /mob/living/silicon/ai)) - message_start = "[name], [speaker.name]" + message_start = span_binary("[name], [speaker.name]") else if (!S.binarycheck()) continue - var/message_to_send = "[message_start] [message_body]" + var/message_to_send = span_binary("[message_start] [message_body]") if(S.check_mentioned(message) && S.is_preference_enabled(/datum/client_preference/check_mention)) message_to_send = "[message_to_send]" S.show_message(message_to_send, 2) @@ -49,7 +49,7 @@ for (var/mob/living/M in listening) if(istype(M, /mob/living/silicon) || M.binarycheck()) continue - M.show_message("synthesised voice beeps, \"beep beep beep\"",2) + M.show_message("synthesised voice beeps, \"beep beep beep\"",2) //robot binary xmitter component power usage if (isrobot(speaker)) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 38669a99e0..ffcea4e93c 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -806,14 +806,14 @@ return -/mob/living/silicon/robot/proc/module_reset() +/mob/living/silicon/robot/proc/module_reset(var/notify = TRUE) transform_with_anim() //VOREStation edit: sprite animation uneq_all() hud_used.update_robot_modules_display(TRUE) modtype = initial(modtype) hands.icon_state = get_hud_module_icon() - - notify_ai(ROBOT_NOTIFICATION_MODULE_RESET, module.name) + if(notify) + notify_ai(ROBOT_NOTIFICATION_MODULE_RESET, module.name) module.Reset(src) module.Destroy() module = null @@ -1468,8 +1468,10 @@ var/obj/item/device/dogborg/sleeper/T = has_upgrade_module(/obj/item/device/dogborg/sleeper) if(T && T.upgraded_capacity) return T - else + else if(!T) return "" // Return this to have the analyzer show an error if the module is missing. FALSE / NULL are used for missing upgrades themselves + else + return FALSE if(given_type == /obj/item/borg/upgrade/advanced/jetpack) return has_upgrade_module(/obj/item/weapon/tank/jetpack/carbondioxide) if(given_type == /obj/item/borg/upgrade/advanced/advhealth) @@ -1488,14 +1490,18 @@ var/obj/item/device/dogborg/sleeper/T = has_upgrade_module(/obj/item/device/dogborg/sleeper) if(T && T.compactor) return T + else if(!T) + return "" // Return this to have the analyzer show an error if the module is missing. FALSE / NULL are used for missing upgrades themselves else - return "" + return FALSE if(given_type == /obj/item/borg/upgrade/restricted/tasercooler) var/obj/item/weapon/gun/energy/taser/mounted/cyborg/T = has_upgrade_module(/obj/item/weapon/gun/energy/taser/mounted/cyborg) if(T && T.recharge_time <= 2) return T + else if(!T) + return "" // Return this to have the analyzer show an error if the module is missing. FALSE / NULL are used for missing upgrades themselves else - return "" + return FALSE if(given_type == /obj/item/borg/upgrade/restricted/advrped) return has_upgrade_module(/obj/item/weapon/storage/part_replacer/adv) if(given_type == /obj/item/borg/upgrade/restricted/diamonddrill) diff --git a/code/modules/mob/new_player/sprite_accessories_ear_vr.dm b/code/modules/mob/new_player/sprite_accessories_ear_vr.dm index d6f7506315..b3850deaa2 100644 --- a/code/modules/mob/new_player/sprite_accessories_ear_vr.dm +++ b/code/modules/mob/new_player/sprite_accessories_ear_vr.dm @@ -456,6 +456,14 @@ color_blend_mode = ICON_MULTIPLY extra_overlay = "vulp_jackal-inner" +/datum/sprite_accessory/ears/fox + name = "fox ears" + desc = "" + icon_state = "fox" + do_colouration = 1 + color_blend_mode = ICON_MULTIPLY + extra_overlay = "fox-inner" + /datum/sprite_accessory/ears/bunny_floppy name = "floopy bunny ears (colorable)" desc = "" @@ -1064,4 +1072,4 @@ icon_state = "triceratops_frill" extra_overlay = "triceratops_frill_spikes" do_colouration = 1 - color_blend_mode = ICON_MULTIPLY \ No newline at end of file + color_blend_mode = ICON_MULTIPLY diff --git a/code/modules/mob/new_player/sprite_accessories_extra_vr.dm b/code/modules/mob/new_player/sprite_accessories_extra_vr.dm index f6ca7340be..1c5be98e82 100644 --- a/code/modules/mob/new_player/sprite_accessories_extra_vr.dm +++ b/code/modules/mob/new_player/sprite_accessories_extra_vr.dm @@ -1139,6 +1139,50 @@ color_blend_mode = ICON_MULTIPLY body_parts = list(BP_TORSO) +//Replikant-specific markings + +/datum/sprite_accessory/marking/replikant/replika_r_thigh + name = "Replikant Stripe - Right Thigh" + icon = 'icons/mob/human_races/markings_vr.dmi' + icon_state = "replika" + color_blend_mode = ICON_MULTIPLY + body_parts = list(BP_R_LEG) + +/datum/sprite_accessory/marking/replikant/replika_r_knee + name = "Replikant Stripe - Right Knee" + icon = 'icons/mob/human_races/markings_vr.dmi' + icon_state = "replika" + color_blend_mode = ICON_MULTIPLY + body_parts = list(BP_R_FOOT) + +/datum/sprite_accessory/marking/replikant/replika_l_thigh + name = "Replikant Stripe - Left Thigh" + icon = 'icons/mob/human_races/markings_vr.dmi' + icon_state = "replika" + color_blend_mode = ICON_MULTIPLY + body_parts = list(BP_L_LEG) + +/datum/sprite_accessory/marking/replikant/replika_l_knee + name = "Replikant Stripe - Left Knee" + icon = 'icons/mob/human_races/markings_vr.dmi' + icon_state = "replika" + color_blend_mode = ICON_MULTIPLY + body_parts = list(BP_L_FOOT) + +/datum/sprite_accessory/marking/replikant/replika_panels_body + name = "Replikant Paneling - SynthFlesh (body)" + icon = 'icons/mob/human_races/markings_vr.dmi' + icon_state = "replikao" + color_blend_mode = ICON_MULTIPLY + body_parts = list(BP_TORSO) + +/datum/sprite_accessory/marking/replikant/replika_panels_groin + name = "Replikant Paneling - SynthFlesh (groin)" + icon = 'icons/mob/human_races/markings_vr.dmi' + icon_state = "replika" + color_blend_mode = ICON_MULTIPLY + body_parts = list(BP_GROIN) + //Digitigrade markings /datum/sprite_accessory/marking/digi icon = 'icons/mob/human_races/markings_digi.dmi' diff --git a/code/modules/mob/new_player/sprite_accessories_taur_vr.dm b/code/modules/mob/new_player/sprite_accessories_taur_vr.dm index 2614db4ef7..49f051662f 100644 --- a/code/modules/mob/new_player/sprite_accessories_taur_vr.dm +++ b/code/modules/mob/new_player/sprite_accessories_taur_vr.dm @@ -755,6 +755,14 @@ clip_mask_icon = null clip_mask_state = null +//grallstonefist: Ranihrönn Skrolk +/datum/sprite_accessory/tail/taur/altmermaid/orcamermaid + name = "Mermaid Orca (Taur)" + icon_state = "orcamermaid_s" + can_ride = 1 + do_colouration = 0 + ckeys_allowed = list("grallstonefist") + //wickedtemp: Chakat Tempest /datum/sprite_accessory/tail/taur/feline/tempest name = "Feline (wickedtemp) (Taur)" diff --git a/code/modules/organs/robolimbs.dm b/code/modules/organs/robolimbs.dm index ff8d1dd5c7..405cb39718 100644 --- a/code/modules/organs/robolimbs.dm +++ b/code/modules/organs/robolimbs.dm @@ -494,6 +494,25 @@ var/const/standard_monitor_styles = "blank=ipc_blank;\ species_alternates = list(SPECIES_HUMAN = "Morgan Trading Co") suggested_species = SPECIES_TESHARI +/datum/robolimb/replika + company = "Replikant" + desc = "An advanced biomechanical prosthetic with pegs for feet." + icon = 'icons/mob/human_races/cyberlimbs/replikant/replikant.dmi' + lifelike = 1 + unavailable_to_build = 1 + modular_bodyparts = MODULAR_BODYPART_PROSTHETIC + parts = list(BP_L_LEG, BP_R_LEG, BP_L_FOOT, BP_R_FOOT) + +/datum/robolimb/replika2 + company = "Replikant - 2nd Gen" + desc = "Modern, second-generation biomechanical prosthetics with pegs for feet." + icon = 'icons/mob/human_races/cyberlimbs/replikant/replikant2.dmi' + lifelike = 1 + unavailable_to_build = 1 + modular_bodyparts = MODULAR_BODYPART_PROSTHETIC + parts = list(BP_L_LEG, BP_R_LEG, BP_L_FOOT, BP_R_FOOT) + + /obj/item/weapon/disk/limb/New(var/newloc) ..() if(company) diff --git a/code/modules/pda/messenger.dm b/code/modules/pda/messenger.dm index 73e0944a65..829fc9a431 100644 --- a/code/modules/pda/messenger.dm +++ b/code/modules/pda/messenger.dm @@ -12,6 +12,7 @@ var/m_hidden = 0 // Is the PDA hidden from the PDA list? var/active_conversation = null // New variable that allows us to only view a single conversation. var/list/conversations = list() // For keeping up with who we have PDA messsages from. + var/list/fakepdas = list() //So that fake PDAs show up in conversations for props. Namedlist of "fakeName" = fakeRef /datum/data/pda/app/messenger/start() . = ..() @@ -42,6 +43,8 @@ convopdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "1"))) else pdas.Add(list(list("Name" = "[P]", "Reference" = "\ref[P]", "Detonate" = "[P.detonate]", "inconvo" = "0"))) + for(var/fakeRef in fakepdas) + convopdas.Add(list(list("Name" = "[fakepdas[fakeRef]]", "Reference" = "[fakeRef]", "Detonate" = "0", "inconvo" = "1"))) data["convopdas"] = convopdas data["pdas"] = pdas @@ -241,4 +244,14 @@ for(var/obj/item/device/pda/target in targets) var/datum/data/pda/app/messenger/P = target.find_program(/datum/data/pda/app/messenger) if(P) - P.receive_message(modified_message, "\ref[M]") \ No newline at end of file + P.receive_message(modified_message, "\ref[M]") + +/* +Generalized proc to handle GM fake prop messages, or future fake prop messages from mapping landmarks. +We need a separate proc for this due to the "target" component and creation of a fake conversation entry. +Invoked by /obj/item/device/pda/proc/createPropFakeConversation_admin(var/mob/M) +*/ +/datum/data/pda/app/messenger/proc/createFakeMessage(fakeName, fakeRef, fakeJob, sent, message) + receive_message(list("sent" = sent, "owner" = "[fakeName]", "job" = "[fakeJob]", "message" = "[message]", "target" = "[fakeRef]"), fakeRef) + if(!fakepdas[fakeRef]) + fakepdas[fakeRef] = fakeName diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index 4c94888c83..dfd733d934 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -1,4 +1,3 @@ - #define NITROGEN_RETARDATION_FACTOR 0.15 //Higher == N2 slows reaction more #define THERMAL_RELEASE_MODIFIER 10000 //Higher == more heat released during reaction #define PHORON_RELEASE_MODIFIER 1500 //Higher == less phoron released by reaction @@ -550,3 +549,26 @@ /obj/item/broken_sm/Destroy() STOP_PROCESSING(SSobj, src) return ..() + +#undef NITROGEN_RETARDATION_FACTOR +#undef THERMAL_RELEASE_MODIFIER +#undef PHORON_RELEASE_MODIFIER +#undef OXYGEN_RELEASE_MODIFIER +#undef REACTION_POWER_MODIFIER + +#undef DETONATION_RADS +#undef DETONATION_MOB_CONCUSSION + +#undef DETONATION_APC_OVERLOAD_PROB +#undef DETONATION_SHUTDOWN_APC +#undef DETONATION_SHUTDOWN_CRITAPC +#undef DETONATION_SHUTDOWN_SMES +#undef DETONATION_SHUTDOWN_RNG_FACTOR +#undef DETONATION_SOLAR_BREAK_CHANCE + +#undef DETONATION_EXPLODE_MIN_POWER +#undef DETONATION_EXPLODE_MAX_POWER + +#undef WARNING_DELAY + +#undef SUPERMATTER_ACCENT_SOUND_COOLDOWN diff --git a/code/modules/reagents/machinery/distillery.dm b/code/modules/reagents/machinery/distillery.dm index 5966f415fd..64475faa06 100644 --- a/code/modules/reagents/machinery/distillery.dm +++ b/code/modules/reagents/machinery/distillery.dm @@ -204,7 +204,7 @@ OutputBeaker = null if("adjust temp") - target_temp = tgui_input_number(usr, "Choose a target temperature.", "Temperature.", T20C) + target_temp = tgui_input_number(usr, "Choose a target temperature.", "Temperature.", T20C, round_value = FALSE) target_temp = CLAMP(target_temp, min_temp, max_temp) update_icon() diff --git a/code/modules/research/message_server.dm b/code/modules/research/message_server.dm index 4a53dcb9d9..5bbe23256a 100644 --- a/code/modules/research/message_server.dm +++ b/code/modules/research/message_server.dm @@ -427,4 +427,7 @@ var/obj/machinery/blackbox_recorder/blackbox if(!FV) return - FV.add_details(details) \ No newline at end of file + FV.add_details(details) + +#undef MESSAGE_SERVER_SPAM_REJECT +#undef MESSAGE_SERVER_DEFAULT_SPAM_LIMIT diff --git a/code/modules/research/rdconsole_tgui.dm b/code/modules/research/rdconsole_tgui.dm index b9683c0f43..24e2a33421 100644 --- a/code/modules/research/rdconsole_tgui.dm +++ b/code/modules/research/rdconsole_tgui.dm @@ -638,3 +638,5 @@ PR.icon_state = "paper_words" PR.forceMove(loc) busy_msg = null + +#undef ENTRIES_PER_RDPAGE diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm index 88e7c90a0f..85b48569ba 100644 --- a/code/modules/tgui/external.dm +++ b/code/modules/tgui/external.dm @@ -36,6 +36,7 @@ * public * * Static Data to be sent to the UI. + * * Static data differs from normal data in that it's large data that should be * sent infrequently. This is implemented optionally for heavy uis that would * be sending a lot of redundant data frequently. Gets squished into one @@ -65,6 +66,17 @@ if(ui) ui.send_full_update() +/** + * public + * + * Will force an update on static data for all viewers. + * Should be done manually whenever something happens to + * change static data. + */ +/datum/proc/update_static_data_for_all_viewers() + for (var/datum/tgui/window as anything in open_uis) + window.send_full_update() + /** * public * @@ -130,7 +142,6 @@ * Associative list of JSON-encoded shared states that were set by * tgui clients. */ - /datum/var/list/tgui_shared_states /** @@ -194,8 +205,7 @@ // Name the verb, and hide it from the user panel. set name = "uiclose" set hidden = TRUE - - var/mob/user = src && src.mob + var/mob/user = src?.mob if(!user) return // Close all tgui datums based on window_id. diff --git a/code/modules/tgui/modules/overmap.dm b/code/modules/tgui/modules/overmap.dm index 2bdca28b93..af3f2b6c8f 100644 --- a/code/modules/tgui/modules/overmap.dm +++ b/code/modules/tgui/modules/overmap.dm @@ -438,7 +438,7 @@ /* END ENGINES */ /* SENSORS */ if("range") - var/nrange = tgui_input_number(usr, "Set new sensors range", "Sensor range", sensors.range) + var/nrange = tgui_input_number(usr, "Set new sensors range", "Sensor range", sensors.range, round_value = FALSE) if(nrange) sensors.set_range(CLAMP(nrange, 1, world.view)) . = TRUE diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index cf75db176c..fd45f9723d 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -11,7 +11,7 @@ var/mob/user /// The object which owns the UI. var/datum/src_object - /// The title of te UI. + /// The title of the UI. var/title /// The window_id for browse() and onclose(). var/datum/tgui_window/window @@ -76,22 +76,29 @@ if(ui_x && ui_y) src.window_size = list(ui_x, ui_y) +/datum/tgui/Destroy() + user = null + src_object = null + return ..() + /** * public * * Open this UI (and initialize it with data). + * + * return bool - TRUE if a new pooled window is opened, FALSE in all other situations including if a new pooled window didn't open because one already exists. */ /datum/tgui/proc/open() if(!user.client) - return null + return FALSE if(window) - return null + return FALSE process_status() if(status < STATUS_UPDATE) - return null + return FALSE window = SStgui.request_pooled_window(user) if(!window) - return null + return FALSE opened_at = world.time window.acquire_lock(src) if(!window.is_ready()) @@ -114,10 +121,14 @@ window.set_mouse_macro() SStgui.on_open(src) + return TRUE + /** * public * - * Close the UI, and all its children. + * Close the UI. + * + * optional can_be_suspended bool */ /datum/tgui/proc/close(can_be_suspended = TRUE, logout = FALSE) if(closing) @@ -146,7 +157,7 @@ * * Enable/disable auto-updating of the UI. * - * required autoupdate bool Enable/disable auto-updating. + * required value bool Enable/disable auto-updating. */ /datum/tgui/proc/set_autoupdate(autoupdate) src.autoupdate = autoupdate @@ -178,6 +189,8 @@ * Makes an asset available to use in tgui. * * required asset datum/asset + * + * return bool - true if an asset was actually sent */ /datum/tgui/proc/send_asset(datum/asset/asset) if(!window) @@ -282,14 +295,12 @@ return // Validate ping if(!initialized && world.time - opened_at > TGUI_PING_TIMEOUT) - // #ifdef TGUI_DEBUGGING // Always log zombie windows log_tgui(user, \ "Error: Zombie window detected, killing it with fire.\n" \ + "window_id: [window.id]\n" \ + "opened_at: [opened_at]\n" \ + "world.time: [world.time]") close(can_be_suspended = FALSE) - // #endif return // Update through a normal call to ui_interact if(status != STATUS_DISABLED && (autoupdate || force)) @@ -321,9 +332,7 @@ /** * private * - * Handle clicks from the UI. - * Call the src_object's ui_act() if status is UI_INTERACTIVE. - * If the src_object's ui_act() returns 1, update all UIs attacked to it. + * Callback for handling incoming tgui messages. */ /datum/tgui/proc/on_message(type, list/payload, list/href_list) // Pass act type messages to tgui_act diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm index 73dfdf2803..62435ac30a 100644 --- a/code/modules/tgui/tgui_window.dm +++ b/code/modules/tgui/tgui_window.dm @@ -1,4 +1,4 @@ -/** +/*! * Copyright (c) 2020 Aleksej Komarov * SPDX-License-Identifier: MIT */ @@ -26,6 +26,18 @@ var/initial_inline_css var/mouse_event_macro_set = FALSE + /** + * Static list used to map in macros that will then emit execute events to the tgui window + * A small disclaimer though I'm no tech wiz: I don't think it's possible to map in right or middle + * clicks in the current state, as they're keywords rather than modifiers. + */ + var/static/list/byondToTguiEventMap = list( + "MouseDown" = "byond/mousedown", + "MouseUp" = "byond/mouseup", + "Ctrl" = "byond/ctrldown", + "Ctrl+UP" = "byond/ctrlup", + ) + /** * public * @@ -165,8 +177,8 @@ * Acquire the window lock. Pool will not be able to provide this window * to other UIs for the duration of the lock. * - * Can be given an optional tgui datum, which will hook its on_message - * callback into the message stream. + * Can be given an optional tgui datum, which will be automatically + * subscribed to incoming messages via the on_message proc. * * optional ui /datum/tgui */ @@ -175,6 +187,8 @@ locked_by = ui /** + * public + * * Release the window lock. */ /datum/tgui_window/proc/release_lock() @@ -391,11 +405,6 @@ if(mouse_event_macro_set) return - var/list/byondToTguiEventMap = list( - "MouseDown" = "byond/mousedown", - "MouseUp" = "byond/mouseup" - ) - for(var/mouseMacro in byondToTguiEventMap) var/command_template = ".output CONTROL PAYLOAD" var/event_message = TGUI_CREATE_MESSAGE(byondToTguiEventMap[mouseMacro], null) @@ -416,10 +425,6 @@ /datum/tgui_window/proc/remove_mouse_macro() if(!mouse_event_macro_set) stack_trace("Unsetting mouse macro on tgui window that has none") - var/list/byondToTguiEventMap = list( - "MouseDown" = "byond/mousedown", - "MouseUp" = "byond/mouseup" - ) for(var/mouseMacro in byondToTguiEventMap) winset(client, null, "[mouseMacro]Window[id]Macro.parent=null") mouse_event_macro_set = FALSE diff --git a/code/modules/tgui_input/checkboxes.dm b/code/modules/tgui_input/checkboxes.dm index 0b1d653955..adac0d0d22 100644 --- a/code/modules/tgui_input/checkboxes.dm +++ b/code/modules/tgui_input/checkboxes.dm @@ -14,13 +14,17 @@ if (!user) user = usr if(!length(items)) - return + return null if (!istype(user)) if (istype(user, /client)) var/client/client = user user = client.mob else - return + return null + + if(isnull(user.client)) + return null + if(!user.client.prefs.tgui_input_mode) return input(user, message, title) as null|anything in items var/datum/tgui_checkbox_input/input = new(user, message, title, items, min_checked, max_checked, timeout, ui_state) @@ -66,7 +70,7 @@ start_time = world.time QDEL_IN(src, timeout) -/datum/tgui_checkbox_input/Destroy(force, ...) +/datum/tgui_checkbox_input/Destroy(force) SStgui.close_uis(src) state = null QDEL_NULL(items) diff --git a/code/modules/tgui_input/list.dm b/code/modules/tgui_input/list.dm index c079f0be74..bd51aee060 100644 --- a/code/modules/tgui_input/list.dm +++ b/code/modules/tgui_input/list.dm @@ -7,28 +7,31 @@ * * message - The content of the input box, shown in the body of the TGUI window. * * title - The title of the input box, shown on the top of the TGUI window. * * items - The options that can be chosen by the user, each string is assigned a button on the UI. - * * default - The option with this value will be selected on first paint of the TGUI window. - * * timeout - The timeout of the input box, after which the input box will close and qdel itself. Set to zero for no timeout. - * * strict_modern - Disabled the preference check of the input box, only allowing the TGUI window to show. + * * default - If an option is already preselected on the UI. Current values, etc. + * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout. */ -/proc/tgui_input_list(mob/user, message, title = "Select", list/items, default, timeout = 0, strict_modern = FALSE) - if (istext(user)) - stack_trace("tgui_alert() received text for user instead of mob") - return +/proc/tgui_input_list(mob/user, message, title = "Select", list/items, default, timeout = 0, strict_modern = FALSE, ui_state = GLOB.tgui_always_state) if (!user) user = usr if(!length(items)) - return + return null if (!istype(user)) if (istype(user, /client)) var/client/client = user user = client.mob else - return + return null + + if(isnull(user.client)) + return null + /// Client does NOT have tgui_input on: Returns regular input if(!user.client.prefs.tgui_input_mode && !strict_modern) return input(user, message, title, default) as null|anything in items - var/datum/tgui_list_input/input = new(user, message, title, items, default, timeout) + var/datum/tgui_list_input/input = new(user, message, title, items, default, timeout, ui_state) + if(input.invalid) + qdel(input) + return input.tgui_interact(user) input.wait() if (input) @@ -48,11 +51,11 @@ var/message /// The list of items (responses) provided on the TGUI window var/list/items - /// Items (strings specifically) mapped to the actual value (e.g. a mob or a verb) + /// Buttons (strings specifically) mapped to the actual value (e.g. a mob or a verb) var/list/items_map /// The button that the user has pressed, null if no selection has been made var/choice - /// The default item to be selected + /// The default button to be selected var/default /// The time at which the tgui_list_input was created, for displaying timeout progress. var/start_time @@ -60,41 +63,42 @@ var/timeout /// Boolean field describing if the tgui_list_input was closed by the user. var/closed + /// The TGUI UI state that will be returned in ui_state(). Default: always_state + var/datum/tgui_state/state + /// Whether the tgui list input is invalid or not (i.e. due to all list entries being null) + var/invalid = FALSE -/datum/tgui_list_input/New(mob/user, message, title, list/items, default, timeout) +/datum/tgui_list_input/New(mob/user, message, title, list/items, default, timeout, ui_state) src.title = title src.message = message src.items = list() src.items_map = list() src.default = default + src.state = ui_state var/list/repeat_items = list() - // Gets rid of illegal characters var/static/regex/whitelistedWords = regex(@{"([^\u0020-\u8000]+)"}) - for(var/i in items) - if(isnull(i)) - stack_trace("Null in a tgui_input_list() items") + if(!i) continue - var/string_key = whitelistedWords.Replace("[i]", "") - //avoids duplicated keys E.g: when areas have the same name string_key = avoid_assoc_duplicate_keys(string_key, repeat_items) - src.items += string_key src.items_map[string_key] = i - + if(length(src.items) == 0) + invalid = TRUE if (timeout) src.timeout = timeout start_time = world.time QDEL_IN(src, timeout) -/datum/tgui_list_input/Destroy(force, ...) +/datum/tgui_list_input/Destroy(force) SStgui.close_uis(src) + state = null QDEL_NULL(items) - . = ..() + return ..() /** * Waits for a user's response to the tgui_list_input's prompt before returning. Returns early if @@ -115,7 +119,7 @@ closed = TRUE /datum/tgui_list_input/tgui_state(mob/user) - return GLOB.tgui_always_state + return state /datum/tgui_list_input/tgui_static_data(mob/user) var/list/data = list() @@ -146,68 +150,9 @@ SStgui.close_uis(src) return TRUE if("cancel") - SStgui.close_uis(src) closed = TRUE + SStgui.close_uis(src) return TRUE /datum/tgui_list_input/proc/set_choice(choice) src.choice = choice - -/** - * Creates an asynchronous TGUI input list window with an associated callback. - * - * This proc should be used to create inputs that invoke a callback with the user's chosen option. - * Arguments: - * * user - The user to show the input box to. - * * message - The content of the input box, shown in the body of the TGUI window. - * * title - The title of the input box, shown on the top of the TGUI window. - * * items - The options that can be chosen by the user, each string is assigned a button on the UI. - * * default - The option with this value will be selected on first paint of the TGUI window. - * * callback - The callback to be invoked when a choice is made. - * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout. - */ -/proc/tgui_input_list_async(mob/user, message, title, list/items, default, datum/callback/callback, timeout = 60 SECONDS) - if (istext(user)) - stack_trace("tgui_alert() received text for user instead of mob") - return - if (!user) - user = usr - if(!length(items)) - return - if (!istype(user)) - if (istype(user, /client)) - var/client/client = user - user = client.mob - else - return - var/datum/tgui_list_input/async/input = new(user, message, title, items, default, callback, timeout) - input.tgui_interact(user) - -/** - * # async tgui_list_input - * - * An asynchronous version of tgui_list_input to be used with callbacks instead of waiting on user responses. - */ -/datum/tgui_list_input/async - /// The callback to be invoked by the tgui_list_input upon having a choice made. - var/datum/callback/callback - -/datum/tgui_list_input/async/New(mob/user, message, title, list/items, default, callback, timeout) - ..(user, title, message, items, default, timeout) - src.callback = callback - -/datum/tgui_list_input/async/Destroy(force, ...) - QDEL_NULL(callback) - . = ..() - -/datum/tgui_list_input/async/tgui_close(mob/user) - . = ..() - qdel(src) - -/datum/tgui_list_input/async/set_choice(choice) - . = ..() - if(!isnull(src.choice)) - callback?.InvokeAsync(src.choice) - -/datum/tgui_list_input/async/wait() - return diff --git a/code/modules/tgui_input/number.dm b/code/modules/tgui_input/number.dm index 4aadb5edc1..f45eaad5c0 100644 --- a/code/modules/tgui_input/number.dm +++ b/code/modules/tgui_input/number.dm @@ -15,7 +15,7 @@ * * timeout - The timeout of the number input, after which the modal will close and qdel itself. Set to zero for no timeout. * * round_value - whether the inputted number is rounded down into an integer. */ -/proc/tgui_input_number(mob/user, message, title = "Number Input", default = 0, max_value = INFINITY, min_value = -INFINITY, timeout = 0, round_value = FALSE) +/proc/tgui_input_number(mob/user, message, title = "Number Input", default = 0, max_value = INFINITY, min_value = -INFINITY, timeout = 0, round_value = TRUE, ui_state = GLOB.tgui_always_state) if (!user) user = usr if (!istype(user)) @@ -23,12 +23,16 @@ var/client/client = user user = client.mob else - return + return null + + if (isnull(user.client)) + return null + // Client does NOT have tgui_input on: Returns regular input if(!user.client.prefs.tgui_input_mode) var/input_number = input(user, message, title, default) as null|num return clamp(round_value ? round(input_number) : input_number, min_value, max_value) - var/datum/tgui_input_number/number_input = new(user, message, title, default, max_value, min_value, timeout, round_value) + var/datum/tgui_input_number/number_input = new(user, message, title, default, max_value, min_value, timeout, round_value, ui_state) number_input.tgui_interact(user) number_input.wait() if (number_input) @@ -62,14 +66,17 @@ var/timeout /// The title of the TGUI window var/title + /// The TGUI UI state that will be returned in ui_state(). Default: always_state + var/datum/tgui_state/state -/datum/tgui_input_number/New(mob/user, message, title, default, max_value, min_value, timeout, round_value) +/datum/tgui_input_number/New(mob/user, message, title, default, max_value, min_value, timeout, round_value, ui_state) src.default = default src.max_value = max_value src.message = message src.min_value = min_value src.title = title src.round_value = round_value + src.state = ui_state if (timeout) src.timeout = timeout start_time = world.time @@ -85,8 +92,9 @@ if(default > max_value) CRASH("Default value is greater than max value.") -/datum/tgui_input_number/Destroy(force, ...) +/datum/tgui_input_number/Destroy(force) SStgui.close_uis(src) + state = null return ..() /** @@ -108,7 +116,7 @@ closed = TRUE /datum/tgui_input_number/tgui_state(mob/user) - return GLOB.tgui_always_state + return state /datum/tgui_input_number/tgui_static_data(mob/user) var/list/data = list() @@ -119,6 +127,7 @@ data["min_value"] = min_value data["swapped_buttons"] = !user.client.prefs.tgui_swapped_buttons data["title"] = title + data["round_value"] = round_value return data /datum/tgui_input_number/tgui_data(mob/user) @@ -135,8 +144,7 @@ if("submit") if(!isnum(params["entry"])) CRASH("A non number was input into tgui input number by [usr]") - //var/choice = round_value ? round(params["entry"]) : params["entry"] - var/choice = params["entry"] + var/choice = round_value ? round(params["entry"]) : params["entry"] if(choice > max_value) CRASH("A number greater than the max value was input into tgui input number by [usr]") if(choice < min_value) @@ -152,60 +160,3 @@ /datum/tgui_input_number/proc/set_entry(entry) src.entry = entry - -/** - * Creates an asynchronous TGUI input num window with an associated callback. - * - * This proc should be used to create inputs that invoke a callback with the user's chosen option. - * Arguments: - * * user - The user to show the input box to. - * * message - The content of the input box, shown in the body of the TGUI window. - * * title - The title of the input box, shown on the top of the TGUI window. - * * default - The default value pre-populated in the input box. - * * callback - The callback to be invoked when a choice is made. - * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout. - * * round_value - whether the inputted number is rounded down into an integer. - */ -/proc/tgui_input_number_async(mob/user, message, title, default, datum/callback/callback, timeout = 60 SECONDS, round_value = FALSE) - if (istext(user)) - stack_trace("tgui_input_num_async() received text for user instead of mob") - return - if (!user) - user = usr - if (!istype(user)) - if (istype(user, /client)) - var/client/client = user - user = client.mob - else - return - var/datum/tgui_input_number/async/input = new(user, message, title, default, callback, timeout, round_value) - input.tgui_interact(user) - -/** - * # async tgui_text_input - * - * An asynchronous version of tgui_text_input to be used with callbacks instead of waiting on user responses. - */ -/datum/tgui_input_number/async - /// The callback to be invoked by the tgui_text_input upon having a choice made. - var/datum/callback/callback - -/datum/tgui_input_number/async/New(mob/user, message, title, default, callback, timeout, round_value) - ..(user, title, message, default, timeout, round_value) - src.callback = callback - -/datum/tgui_input_number/async/Destroy(force, ...) - QDEL_NULL(callback) - . = ..() - -/datum/tgui_input_number/async/tgui_close(mob/user) - . = ..() - qdel(src) - -/datum/tgui_input_number/async/set_entry(entry) - . = ..() - if(!isnull(src.entry)) - callback?.InvokeAsync(src.entry) - -/datum/tgui_input_number/async/wait() - return \ No newline at end of file diff --git a/code/modules/tgui_input/text.dm b/code/modules/tgui_input/text.dm index 614f9e142e..8dc1e5a6c4 100644 --- a/code/modules/tgui_input/text.dm +++ b/code/modules/tgui_input/text.dm @@ -16,9 +16,6 @@ * * timeout - The timeout of the textbox, after which the modal will close and qdel itself. Set to zero for no timeout. */ /proc/tgui_input_text(mob/user, message = "", title = "Text Input", default, max_length = INFINITY, multiline = FALSE, encode = FALSE, timeout = 0, prevent_enter = FALSE) - if (istext(user)) - stack_trace("tgui_input_text() received text for user instead of mob") - return if (!user) user = usr if (!istype(user)) @@ -27,6 +24,10 @@ user = client.mob else return + + if(isnull(user.client)) + return null + // Client does NOT have tgui_input on: Returns regular input if(!user.client.prefs.tgui_input_mode) if(encode) @@ -40,11 +41,7 @@ else return input(user, message, title, default) as text|null - //Client has TGUI input lock on; override whatever prevent_enter was specified beforehand - if(user.client.prefs.tgui_input_lock) - prevent_enter = TRUE - - var/datum/tgui_input_text/text_input = new(user, message, title, default, max_length, multiline, encode, timeout, prevent_enter) + var/datum/tgui_input_text/text_input = new(user, message, title, default, max_length, multiline, encode, timeout) text_input.tgui_interact(user) text_input.wait() if (text_input) @@ -79,9 +76,7 @@ /// The title of the TGUI window var/title - var/prevent_enter - -/datum/tgui_input_text/New(mob/user, message, title, default, max_length, multiline, encode, timeout, prevent_enter) +/datum/tgui_input_text/New(mob/user, message, title, default, max_length, multiline, encode, timeout) src.default = default src.encode = encode src.max_length = max_length @@ -92,18 +87,17 @@ src.timeout = timeout start_time = world.time QDEL_IN(src, timeout) - src.prevent_enter = prevent_enter -/datum/tgui_input_text/Destroy(force, ...) +/datum/tgui_input_text/Destroy(force) SStgui.close_uis(src) - . = ..() + return ..() /** * Waits for a user's response to the tgui_text_input's prompt before returning. Returns early if * the window was closed by the user. */ /datum/tgui_input_text/proc/wait() - while (!entry && !closed) + while (!entry && !closed && !QDELETED(src)) stoplag(1) /datum/tgui_input_text/tgui_interact(mob/user, datum/tgui/ui) @@ -128,7 +122,6 @@ data["placeholder"] = default // Default is a reserved keyword data["swapped_buttons"] = !user.client.prefs.tgui_swapped_buttons data["title"] = title - data["prevent_enter"] = prevent_enter return data /datum/tgui_input_text/tgui_data(mob/user) @@ -143,77 +136,27 @@ return switch(action) if("submit") - if(length(params["entry"]) > max_length) - return - if(encode && (length(html_encode(params["entry"])) > max_length)) - to_chat(usr, span_notice("Your message was clipped due to special character usage.")) + if(max_length) + if(length(params["entry"]) > max_length) + CRASH("[usr] typed a text string longer than the max length") + if(encode && (length(html_encode(params["entry"])) > max_length)) + to_chat(usr, span_notice("Your message was clipped due to special character usage.")) set_entry(params["entry"]) closed = TRUE SStgui.close_uis(src) return TRUE if("cancel") - SStgui.close_uis(src) closed = TRUE + SStgui.close_uis(src) return TRUE +/** + * Sets the return value for the tgui text proc. + * If html encoding is enabled, the text will be encoded. + * This can sometimes result in a string that is longer than the max length. + * If the string is longer than the max length, it will be clipped. + */ /datum/tgui_input_text/proc/set_entry(entry) if(!isnull(entry)) var/converted_entry = encode ? html_encode(entry) : entry - //converted_entry = readd_quotes(converted_entry) src.entry = trim(converted_entry, max_length) - -/** - * Creates an asynchronous TGUI input text window with an associated callback. - * - * This proc should be used to create inputs that invoke a callback with the user's chosen option. - * Arguments: - * * user - The user to show the input box to. - * * message - The content of the input box, shown in the body of the TGUI window. - * * title - The title of the input box, shown on the top of the TGUI window. - * * default - The default value pre-populated in the input box. - * * callback - The callback to be invoked when a choice is made. - * * timeout - The timeout of the input box, after which the menu will close and qdel itself. Set to zero for no timeout. - */ -/proc/tgui_input_text_async(mob/user, message, title, default, datum/callback/callback, max_length, multiline, encode, timeout = 60 SECONDS) - if (istext(user)) - stack_trace("tgui_input_text_async() received text for user instead of mob") - return - if (!user) - user = usr - if (!istype(user)) - if (istype(user, /client)) - var/client/client = user - user = client.mob - else - return - var/datum/tgui_input_text/async/input = new(user, message, title, default, callback, max_length, multiline, encode, timeout) - input.tgui_interact(user) - -/** - * # async tgui_text_input - * - * An asynchronous version of tgui_text_input to be used with callbacks instead of waiting on user responses. - */ -/datum/tgui_input_text/async - /// The callback to be invoked by the tgui_text_input upon having a choice made. - var/datum/callback/callback - -/datum/tgui_input_text/async/New(mob/user, message, title, default, callback, max_length, multiline, encode, timeout) - ..(user, title, message, default, max_length, multiline, encode, timeout) - src.callback = callback - -/datum/tgui_input_text/async/Destroy(force, ...) - QDEL_NULL(callback) - . = ..() - -/datum/tgui_input_text/async/tgui_close(mob/user) - . = ..() - qdel(src) - -/datum/tgui_input_text/async/set_entry(entry) - . = ..() - if(!isnull(src.entry)) - callback?.InvokeAsync(src.entry) - -/datum/tgui_input_text/async/wait() - return diff --git a/code/modules/vore/eating/belly_obj_vr.dm b/code/modules/vore/eating/belly_obj_vr.dm index f2852621ec..b147915454 100644 --- a/code/modules/vore/eating/belly_obj_vr.dm +++ b/code/modules/vore/eating/belly_obj_vr.dm @@ -1,6 +1,3 @@ -#define VORE_SOUND_FALLOFF 0.1 -#define VORE_SOUND_RANGE 3 - // // Belly system 2.0, now using objects instead of datums because EH at datums. // How many times have I rewritten bellies and vore now? -Aro diff --git a/code/modules/vore/eating/silicon_vr.dm b/code/modules/vore/eating/silicon_vr.dm index e43b736f0e..2334e186e8 100644 --- a/code/modules/vore/eating/silicon_vr.dm +++ b/code/modules/vore/eating/silicon_vr.dm @@ -1,9 +1,3 @@ -//Dat AI vore yo -#define HOLO_ORIGINAL_COLOR null //Original hologram color: "#7db4e1" -#define HOLO_HARDLIGHT_COLOR "#d97de0" -#define HOLO_ORIGINAL_ALPHA 120 -#define HOLO_HARDLIGHT_ALPHA 200 - /obj/effect/overlay/aiholo var/mob/living/bellied //Only belly one person at a time. No huge vore-organs setup for AIs. var/mob/living/silicon/ai/master //This will receive the AI controlling the Hologram. For referencing purposes. diff --git a/code/modules/vore/eating/vore_vr.dm b/code/modules/vore/eating/vore_vr.dm index ae8fe5151d..671416df9d 100644 --- a/code/modules/vore/eating/vore_vr.dm +++ b/code/modules/vore/eating/vore_vr.dm @@ -19,8 +19,6 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE -Aro <3 */ -#define VORE_VERSION 2 //This is a Define so you don't have to worry about magic numbers. - // // Overrides/additions to stock defines go here, as well as hooks. Sort them by // the object they are overriding. So all /mob/living together, etc. diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm index 7a7878ee45..e16f9f773e 100644 --- a/code/modules/vore/eating/vorepanel_vr.dm +++ b/code/modules/vore/eating/vorepanel_vr.dm @@ -2,12 +2,6 @@ // Vore management panel for players // -#define BELLIES_MAX 40 -#define BELLIES_NAME_MIN 2 -#define BELLIES_NAME_MAX 40 -#define BELLIES_DESC_MAX 4096 -#define FLAVOR_MAX 400 - //INSERT COLORIZE-ONLY STOMACHS HERE var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", "a_synth_flesh_mono_hole", diff --git a/code/modules/vore/resizing/sizegun_slow_vr.dm b/code/modules/vore/resizing/sizegun_slow_vr.dm index 648c0f91c7..9a993e7a87 100644 --- a/code/modules/vore/resizing/sizegun_slow_vr.dm +++ b/code/modules/vore/resizing/sizegun_slow_vr.dm @@ -3,7 +3,7 @@ /obj/item/device/slow_sizegun name = "gradual size gun" - desc = "A highly advanced ray gun, designed for progressive and gradual changing of size." + desc = "A highly advanced ray gun, designed for progressive and gradual changing of size. Size trading can be toggled on via alt-clicking." icon = 'icons/obj/gun_vr.dmi' icon_state = "sizegun-old-0" var/base_icon_state = "sizegun-old" @@ -17,6 +17,7 @@ var/dorm_size = TRUE var/size_increment = 0.01 var/current_target + var/trading = 0 /obj/item/device/slow_sizegun/update_icon() icon_state = "[base_icon_state]-[sizeshift_mode]" @@ -50,6 +51,9 @@ if(unresizable) return TRUE + if(trading == 1 && !(user.resizable)) + return TRUE + if(!(target.has_large_resize_bounds()) && (target.size_multiplier >= RESIZE_MAXIMUM) && sizeshift_mode == SIZE_GROW) return TRUE @@ -62,6 +66,18 @@ if(target.size_multiplier <= RESIZE_MINIMUM_DORMS && sizeshift_mode == SIZE_SHRINK) return TRUE + if(trading == 1 && !(user.has_large_resize_bounds()) && (user.size_multiplier >= RESIZE_MAXIMUM) && sizeshift_mode == SIZE_GROW) + return TRUE + + if(trading == 1 && user.size_multiplier >= RESIZE_MAXIMUM_DORMS && sizeshift_mode == SIZE_GROW) + return TRUE + + if(trading == 1 && !(user.has_large_resize_bounds()) && (user.size_multiplier <= RESIZE_MINIMUM) && sizeshift_mode == SIZE_SHRINK) + return TRUE + + if(trading == 1 && user.size_multiplier <= RESIZE_MINIMUM_DORMS && sizeshift_mode == SIZE_SHRINK) + return TRUE + return FALSE /obj/item/device/slow_sizegun/afterattack(atom/target, mob/user, proximity_flag) @@ -81,6 +97,7 @@ return var/mob/living/L = target + var/mob/living/U = user if(get_dist(target, user) > beam_range) to_chat(user, span("warning", "You are too far away from \the [target] to affect it. Get closer.")) @@ -100,6 +117,9 @@ if(!(L.resizable)) unresizable = TRUE + if(trading == 1 && !(user.resizable)) + unresizable = TRUE + if(unresizable) to_chat(user, span("warning", "\the [target] is immune to resizing.")) @@ -119,14 +139,25 @@ var/active_hand = user.get_active_hand() - while(!should_stop(target, user, active_hand)) - stoplag(3) + if (trading == 0) + while(!should_stop(target, user, active_hand)) + stoplag(3) - if(sizeshift_mode == SIZE_SHRINK) - L.resize((L.size_multiplier - size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE) - else if(sizeshift_mode == SIZE_GROW) - L.resize((L.size_multiplier + size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE) + if(sizeshift_mode == SIZE_SHRINK) + L.resize((L.size_multiplier - size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE) + else if(sizeshift_mode == SIZE_GROW) + L.resize((L.size_multiplier + size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE) + if (trading == 1) + while(!should_stop(target, user, active_hand)) + stoplag(3) + + if(sizeshift_mode == SIZE_SHRINK) + L.resize((L.size_multiplier - size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE) + U.resize((U.size_multiplier + size_increment), uncapped = U.has_large_resize_bounds(), aura_animation = FALSE) + else if(sizeshift_mode == SIZE_GROW) + L.resize((L.size_multiplier + size_increment), uncapped = L.has_large_resize_bounds(), aura_animation = FALSE) + U.resize((U.size_multiplier - size_increment), uncapped = U.has_large_resize_bounds(), aura_animation = FALSE) busy = FALSE current_target = null @@ -219,3 +250,13 @@ /obj/item/device/slow_sizegun/proc/color_box(list/box_segments, new_color, new_time) for(var/i in box_segments) animate(i, color = new_color, time = new_time) + +//Alt click to activate size trading + +/obj/item/device/slow_sizegun/AltClick(mob/user) + if (trading == 0) + trading = 1 + to_chat(user, span("notice", "\The [src] will now trade your targets size for your own.")) + else + trading = 0 + to_chat(user, span("notice", "\The [src] will no longer trade your targets size for your own.")) diff --git a/code/modules/vore/resizing/sizegun_vr.dm b/code/modules/vore/resizing/sizegun_vr.dm index c5fc1f3648..982d5185f9 100644 --- a/code/modules/vore/resizing/sizegun_vr.dm +++ b/code/modules/vore/resizing/sizegun_vr.dm @@ -26,11 +26,20 @@ /obj/item/weapon/gun/energy/sizegun/New() ..() verbs += PROC_REF(select_size) + verbs += PROC_REF(spin_dial) /obj/item/weapon/gun/energy/sizegun/attack_self(mob/user) . = ..() select_size() +/obj/item/weapon/gun/energy/sizegun/proc/spin_dial() + set name = "Spin Size Dial" + set category = "Object" + set src in view(1) + + size_set_to = (rand(25,200)) /100 + usr.visible_message("\The [usr] spins the size dial to a random value!","You spin the dial to a random value!") + /obj/item/weapon/gun/energy/sizegun/consume_next_projectile() . = ..() var/obj/item/projectile/beam/sizelaser/G = . diff --git a/code/modules/xgm/xgm_gas_mixture.dm b/code/modules/xgm/xgm_gas_mixture.dm index fd52efca22..eb9c718014 100644 --- a/code/modules/xgm/xgm_gas_mixture.dm +++ b/code/modules/xgm/xgm_gas_mixture.dm @@ -189,6 +189,7 @@ //var/partial_pressure = gas[gasid] * R_IDEAL_GAS_EQUATION * temperature / volume //return R_IDEAL_GAS_EQUATION * ( log (1 + IDEAL_GAS_ENTROPY_CONSTANT/partial_pressure) + 20 ) +#undef SPECIFIC_ENTROPY_VACUUM //Updates the total_moles count and trims any empty gases. /datum/gas_mixture/proc/update_values() diff --git a/code/unit_tests/zas_tests.dm b/code/unit_tests/zas_tests.dm index 8fe6d90369..29e4bd1b70 100644 --- a/code/unit_tests/zas_tests.dm +++ b/code/unit_tests/zas_tests.dm @@ -121,3 +121,7 @@ /datum/unit_test/zas_area_test/cargo_bay name = "ZAS: Cargo Bay" area_path = /area/quartermaster/storage + +#undef UT_NORMAL +#undef UT_VACUUM +#undef UT_NORMAL_COLD diff --git a/icons/inventory/accessory/item.dmi b/icons/inventory/accessory/item.dmi index 1c60ccfee6..2fc09f9efb 100644 Binary files a/icons/inventory/accessory/item.dmi and b/icons/inventory/accessory/item.dmi differ diff --git a/icons/inventory/accessory/item_vr.dmi b/icons/inventory/accessory/item_vr.dmi index de59e5e17d..5a85d7bb33 100644 Binary files a/icons/inventory/accessory/item_vr.dmi and b/icons/inventory/accessory/item_vr.dmi differ diff --git a/icons/inventory/accessory/mob.dmi b/icons/inventory/accessory/mob.dmi index 8fb324c931..dae029fcde 100644 Binary files a/icons/inventory/accessory/mob.dmi and b/icons/inventory/accessory/mob.dmi differ diff --git a/icons/inventory/head/item_vr.dmi b/icons/inventory/head/item_vr.dmi index 216a518951..813586c308 100644 Binary files a/icons/inventory/head/item_vr.dmi and b/icons/inventory/head/item_vr.dmi differ diff --git a/icons/inventory/head/mob_vr.dmi b/icons/inventory/head/mob_vr.dmi index b182d2c1c1..71f9907513 100644 Binary files a/icons/inventory/head/mob_vr.dmi and b/icons/inventory/head/mob_vr.dmi differ diff --git a/icons/inventory/uniform/item.dmi b/icons/inventory/uniform/item.dmi index c059c0f27d..9e143e828c 100644 Binary files a/icons/inventory/uniform/item.dmi and b/icons/inventory/uniform/item.dmi differ diff --git a/icons/inventory/uniform/item_vr.dmi b/icons/inventory/uniform/item_vr.dmi index 39f4eece02..a5d3708c0a 100644 Binary files a/icons/inventory/uniform/item_vr.dmi and b/icons/inventory/uniform/item_vr.dmi differ diff --git a/icons/inventory/uniform/mob.dmi b/icons/inventory/uniform/mob.dmi index 0b39bba6d2..17c2bde39f 100644 Binary files a/icons/inventory/uniform/mob.dmi and b/icons/inventory/uniform/mob.dmi differ diff --git a/icons/inventory/uniform/mob_vr.dmi b/icons/inventory/uniform/mob_vr.dmi index 9e1fc59a1c..25b58456e1 100644 Binary files a/icons/inventory/uniform/mob_vr.dmi and b/icons/inventory/uniform/mob_vr.dmi differ diff --git a/icons/mob/human_face_m.dmi b/icons/mob/human_face_m.dmi index 7986fb844f..109ca2eea5 100644 Binary files a/icons/mob/human_face_m.dmi and b/icons/mob/human_face_m.dmi differ diff --git a/icons/mob/human_races/cyberlimbs/replikant/replikant.dmi b/icons/mob/human_races/cyberlimbs/replikant/replikant.dmi new file mode 100644 index 0000000000..548eadce32 Binary files /dev/null and b/icons/mob/human_races/cyberlimbs/replikant/replikant.dmi differ diff --git a/icons/mob/human_races/cyberlimbs/replikant/replikant2.dmi b/icons/mob/human_races/cyberlimbs/replikant/replikant2.dmi new file mode 100644 index 0000000000..a1fc17ac38 Binary files /dev/null and b/icons/mob/human_races/cyberlimbs/replikant/replikant2.dmi differ diff --git a/icons/mob/human_races/markings_vr.dmi b/icons/mob/human_races/markings_vr.dmi index 37d9c6cf47..df7e6252aa 100644 Binary files a/icons/mob/human_races/markings_vr.dmi and b/icons/mob/human_races/markings_vr.dmi differ diff --git a/icons/mob/items/lefthand_hats.dmi b/icons/mob/items/lefthand_hats.dmi index bf46837f84..abf3b7f620 100644 Binary files a/icons/mob/items/lefthand_hats.dmi and b/icons/mob/items/lefthand_hats.dmi differ diff --git a/icons/mob/items/righthand_hats.dmi b/icons/mob/items/righthand_hats.dmi index 29fa46122b..40cdb7a3e4 100644 Binary files a/icons/mob/items/righthand_hats.dmi and b/icons/mob/items/righthand_hats.dmi differ diff --git a/icons/mob/vore/ears_vr.dmi b/icons/mob/vore/ears_vr.dmi index a7b1f425b0..f49006df1c 100644 Binary files a/icons/mob/vore/ears_vr.dmi and b/icons/mob/vore/ears_vr.dmi differ diff --git a/icons/mob/vore/taurs_vr.dmi b/icons/mob/vore/taurs_vr.dmi index a1ce93c081..77be24a36c 100644 Binary files a/icons/mob/vore/taurs_vr.dmi and b/icons/mob/vore/taurs_vr.dmi differ diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm index f7f9e41cbe..623e41ac94 100644 --- a/interface/stylesheet.dm +++ b/interface/stylesheet.dm @@ -75,6 +75,10 @@ em {font-style: normal;font-weight: bold;} .srvradio {color: #6eaa2c;} .expradio {color: #555555;} +/* Global Languages */ +.hivemind {font-style: italic;} +.binarysay {font-style: italic;} + /* Miscellaneous */ .name {font-weight: bold;} .say {} diff --git a/tgui/.eslintignore b/tgui/.eslintignore index a59187b933..d3c0ac79cd 100644 --- a/tgui/.eslintignore +++ b/tgui/.eslintignore @@ -3,4 +3,14 @@ /**/*.bundle.* /**/*.chunk.* /**/*.hot-update.* -/packages/inferno/** +**.lock +**.log +**.json +**.svg +**.scss +**.md +**.css +**.txt +**.woff2 +**.eot +**.ttf diff --git a/tgui/.eslintrc-harder.yml b/tgui/.eslintrc-harder.yml deleted file mode 100644 index d466125967..0000000000 --- a/tgui/.eslintrc-harder.yml +++ /dev/null @@ -1,43 +0,0 @@ -rules: - ## Enforce a maximum cyclomatic complexity allowed in a program - # complexity: [warn, { max: 25 }] - ## Enforce consistent brace style for blocks - # brace-style: [warn, stroustrup, { allowSingleLine: false }] - ## Enforce the consistent use of either backticks, double, or single quotes - # quotes: [warn, single, { - # avoidEscape: true, - # allowTemplateLiterals: true, - # }] - # react/jsx-closing-bracket-location: [warn, { - # selfClosing: after-props, - # nonEmpty: after-props, - # }] - # react/display-name: warn - - ## Radar - ## ------------------------------------------------------ - # radar/cognitive-complexity: warn - radar/max-switch-cases: warn - radar/no-all-duplicated-branches: warn - radar/no-collapsible-if: warn - radar/no-collection-size-mischeck: warn - radar/no-duplicate-string: warn - radar/no-duplicated-branches: warn - radar/no-element-overwrite: warn - radar/no-extra-arguments: warn - radar/no-identical-conditions: warn - radar/no-identical-expressions: warn - radar/no-identical-functions: warn - radar/no-inverted-boolean-check: warn - radar/no-one-iteration-loop: warn - radar/no-redundant-boolean: warn - radar/no-redundant-jump: warn - radar/no-same-line-conditional: warn - radar/no-small-switch: warn - radar/no-unused-collection: warn - radar/no-use-of-empty-return-value: warn - radar/no-useless-catch: warn - radar/prefer-immediate-return: warn - radar/prefer-object-literal: warn - radar/prefer-single-boolean-return: warn - radar/prefer-while: warn diff --git a/tgui/.eslintrc-sonar.yml b/tgui/.eslintrc-sonar.yml new file mode 100644 index 0000000000..ebc57f72c0 --- /dev/null +++ b/tgui/.eslintrc-sonar.yml @@ -0,0 +1 @@ +extends: 'plugin:sonarjs/recommended' diff --git a/tgui/.eslintrc.yml b/tgui/.eslintrc.yml index 4f7e8f02df..92dfe1320c 100644 --- a/tgui/.eslintrc.yml +++ b/tgui/.eslintrc.yml @@ -11,14 +11,14 @@ env: browser: true node: true plugins: - - radar + - sonarjs - react - unused-imports + - simple-import-sort settings: react: - version: '16.10' + version: '18.2' rules: - ## Possible Errors ## ---------------------------------------- ## Enforce “for” loop update clause moving the counter in the right @@ -309,13 +309,16 @@ rules: ## Enforce or disallow capitalization of the first letter of a comment # capitalized-comments: error ## Require or disallow trailing commas - comma-dangle: [error, { - arrays: always-multiline, - objects: always-multiline, - imports: always-multiline, - exports: always-multiline, - functions: only-multiline, ## Optional on functions - }] + comma-dangle: [ + error, + { + arrays: always-multiline, + objects: always-multiline, + imports: always-multiline, + exports: always-multiline, + functions: only-multiline, ## Optional on functions + }, + ] ## Enforce consistent spacing before and after commas comma-spacing: [error, { before: false, after: true }] ## Enforce consistent comma style @@ -335,7 +338,7 @@ rules: ## Require or disallow named function expressions # func-names: error ## Enforce the consistent use of either function declarations or expressions - func-style: [error, expression] + # func-style: [error, expression] ## Enforce line breaks between arguments of a function call # function-call-argument-newline: error ## Enforce consistent line breaks inside function parentheses @@ -350,15 +353,15 @@ rules: ## Enforce the location of arrow function bodies # implicit-arrow-linebreak: error ## Enforce consistent indentation - # indent: [warn, 2, { SwitchCase: 1 }] + # indent: [error, 2, { SwitchCase: 1 }] ## Enforce the consistent use of either double or single quotes in JSX ## attributes - # jsx-quotes: [warn, prefer-double] + # jsx-quotes: [error, prefer-double] ## Enforce consistent spacing between keys and values in object literal ## properties - # key-spacing: [warn, { beforeColon: false, afterColon: true }] + # key-spacing: [error, { beforeColon: false, afterColon: true }] ## Enforce consistent spacing before and after keywords - # keyword-spacing: [warn, { before: true, after: true }] + # keyword-spacing: [error, { before: true, after: true }] ## Enforce position of line comments # line-comment-position: error ## Enforce consistent linebreak style @@ -370,8 +373,8 @@ rules: ## Enforce a maximum depth that blocks can be nested # max-depth: error ## Enforce a maximum line length - # max-len: [warn, { - # code: 120, + # max-len: [error, { + # code: 80, # ## Ignore imports # ignorePattern: '^(import\s.+\sfrom\s|.*require\()', # ignoreUrls: true, @@ -415,7 +418,7 @@ rules: ## Disallow mixed binary operators # no-mixed-operators: error ## Disallow mixed spaces and tabs for indentation - no-mixed-spaces-and-tabs: warn + # no-mixed-spaces-and-tabs: error ## Disallow use of chained assignment expressions # no-multi-assign: error ## Disallow multiple empty lines @@ -441,7 +444,7 @@ rules: ## Disallow ternary operators when simpler alternatives exist # no-unneeded-ternary: error ## Disallow whitespace before properties - # no-whitespace-before-property: warn + # no-whitespace-before-property: error ## Enforce the location of single-line statements # nonblock-statement-body-position: error ## Enforce consistent line breaks inside braces @@ -458,7 +461,7 @@ rules: ## Require or disallow assignment operator shorthand where possible # operator-assignment: error ## Enforce consistent linebreak style for operators - # operator-linebreak: [warn, before] + # operator-linebreak: [error, before] ## Require or disallow padding within blocks # padded-blocks: error ## Require or disallow padding lines between statements @@ -483,7 +486,7 @@ rules: ## Enforce consistent spacing before blocks space-before-blocks: [error, always] ## Enforce consistent spacing before function definition opening parenthesis - # space-before-function-paren: [warn, { + # space-before-function-paren: [error, { # anonymous: always, # named: never, # asyncArrow: always, @@ -696,7 +699,7 @@ rules: react/jsx-closing-tag-location: error ## Enforce or disallow newlines inside of curly braces in JSX attributes and ## expressions (fixable) - # react/jsx-curly-newline: warn + # react/jsx-curly-newline: error ## Enforce or disallow spaces inside of curly braces in JSX attributes and ## expressions (fixable) react/jsx-curly-spacing: error @@ -709,13 +712,13 @@ rules: ## Enforce event handler naming conventions in JSX react/jsx-handler-names: error ## Validate JSX indentation (fixable) - # react/jsx-indent: [warn, 2, { + # react/jsx-indent: [error, 2, { # checkAttributes: true, # }] ## Validate props indentation in JSX (fixable) - # react/jsx-indent-props: [warn, 2] + # react/jsx-indent-props: [error, 2] ## Validate JSX has key prop when in array or iterator - # react/jsx-key: warn + react/jsx-key: error ## Validate JSX maximum depth react/jsx-max-depth: [error, { max: 10 }] ## Generous ## Limit maximum of props on a single line in JSX (fixable) @@ -762,3 +765,6 @@ rules: ## Prevents the use of unused imports. ## This could be done by enabling no-unused-vars, but we're doing this for now unused-imports/no-unused-imports: error + ## https://github.com/lydell/eslint-plugin-simple-import-sort/ + simple-import-sort/imports: error + simple-import-sort/exports: error diff --git a/tgui/.gitignore b/tgui/.gitignore index 7214051f09..eb4f718b1b 100644 --- a/tgui/.gitignore +++ b/tgui/.gitignore @@ -17,6 +17,7 @@ package-lock.json /public/*.map /public/tgui-bench.bundle.js /public/tgui-bench.bundle.css +/coverage ## Previously ignored locations that are kept to avoid confusing git ## while transitioning to a new project structure. diff --git a/tgui/.prettierrc.yml b/tgui/.prettierrc.yml index 1eebe6098b..0176969226 100644 --- a/tgui/.prettierrc.yml +++ b/tgui/.prettierrc.yml @@ -1,15 +1 @@ -arrowParens: always -breakLongMethodChains: true -endOfLine: lf -importFormatting: oneline -jsxBracketSameLine: true -jsxSingleQuote: false -offsetTernaryExpressions: true -printWidth: 80 -proseWrap: preserve -quoteProps: preserve -semi: true singleQuote: true -tabWidth: 2 -trailingComma: es5 -useTabs: false diff --git a/tgui/.swcrc b/tgui/.swcrc new file mode 100644 index 0000000000..c0402a41f0 --- /dev/null +++ b/tgui/.swcrc @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/swcrc", + "jsc": { + "loose": true, + "parser": { + "syntax": "typescript", + "tsx": true + }, + "transform": { + "react": { + "runtime": "automatic" + } + } + } +} diff --git a/tgui/.yarn/sdks/eslint/package.json b/tgui/.yarn/sdks/eslint/package.json index 744a773210..b29322a1ff 100644 --- a/tgui/.yarn/sdks/eslint/package.json +++ b/tgui/.yarn/sdks/eslint/package.json @@ -2,5 +2,8 @@ "name": "eslint", "version": "7.32.0-sdk", "main": "./lib/api.js", - "type": "commonjs" + "type": "commonjs", + "bin": { + "eslint": "./bin/eslint.js" + } } diff --git a/tgui/.yarn/sdks/prettier/index.cjs b/tgui/.yarn/sdks/prettier/index.cjs new file mode 100644 index 0000000000..d42808285a --- /dev/null +++ b/tgui/.yarn/sdks/prettier/index.cjs @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const { existsSync } = require(`fs`); +const { createRequire, createRequireFromPath } = require(`module`); +const { resolve } = require(`path`); + +const relPnpApiPath = '../../../.pnp.cjs'; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require prettier + require(absPnpApiPath).setup(); + } +} + +// Defer to the real prettier your application uses +module.exports = absRequire(`prettier`); diff --git a/tgui/.yarn/sdks/prettier/index.js b/tgui/.yarn/sdks/prettier/index.js deleted file mode 100644 index f6882d8097..0000000000 --- a/tgui/.yarn/sdks/prettier/index.js +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env node - -const {existsSync} = require(`fs`); -const {createRequire, createRequireFromPath} = require(`module`); -const {resolve} = require(`path`); - -const relPnpApiPath = "../../../.pnp.cjs"; - -const absPnpApiPath = resolve(__dirname, relPnpApiPath); -const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); - -if (existsSync(absPnpApiPath)) { - if (!process.versions.pnp) { - // Setup the environment to be able to require prettier/index.js - require(absPnpApiPath).setup(); - } -} - -// Defer to the real prettier/index.js your application uses -module.exports = absRequire(`prettier/index.js`); diff --git a/tgui/.yarn/sdks/prettier/package.json b/tgui/.yarn/sdks/prettier/package.json index 0cbd71ff32..c61f5117ba 100644 --- a/tgui/.yarn/sdks/prettier/package.json +++ b/tgui/.yarn/sdks/prettier/package.json @@ -1,6 +1,7 @@ { "name": "prettier", - "version": "0.19.0-sdk", - "main": "./index.js", - "type": "commonjs" + "version": "3.1.0-sdk", + "main": "./index.cjs", + "type": "commonjs", + "bin": "./bin/prettier.cjs" } diff --git a/tgui/.yarn/sdks/prettier/prettier.cjs b/tgui/.yarn/sdks/prettier/prettier.cjs new file mode 100644 index 0000000000..5efad688e7 --- /dev/null +++ b/tgui/.yarn/sdks/prettier/prettier.cjs @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = createRequire(absPnpApiPath); + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require prettier/bin/prettier.cjs + require(absPnpApiPath).setup(); + } +} + +// Defer to the real prettier/bin/prettier.cjs your application uses +module.exports = absRequire(`prettier/bin/prettier.cjs`); diff --git a/tgui/.yarn/sdks/typescript/lib/tsserver.js b/tgui/.yarn/sdks/typescript/lib/tsserver.js index 9f9f4d6f46..e1adb497b6 100644 --- a/tgui/.yarn/sdks/typescript/lib/tsserver.js +++ b/tgui/.yarn/sdks/typescript/lib/tsserver.js @@ -1,29 +1,31 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, createRequireFromPath} = require(`module`); -const {resolve} = require(`path`); +const { existsSync } = require(`fs`); +const { createRequire, createRequireFromPath } = require(`module`); +const { resolve } = require(`path`); -const relPnpApiPath = "../../../../.pnp.cjs"; +const relPnpApiPath = '../../../../.pnp.cjs'; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); -const moduleWrapper = tsserver => { +const moduleWrapper = (tsserver) => { if (!process.versions.pnp) { return tsserver; } - const {isAbsolute} = require(`path`); + const { isAbsolute } = require(`path`); const pnpApi = require(`pnpapi`); - const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); - const isPortal = str => str.startsWith("portal:/"); - const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + const isVirtual = (str) => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = (str) => str.startsWith('portal:/'); + const normalize = (str) => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); - const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { - return `${locator.name}@${locator.reference}`; - })); + const dependencyTreeRoots = new Set( + pnpApi.getDependencyTreeRoots().map((locator) => { + return `${locator.name}@${locator.reference}`; + }) + ); // VSCode sends the zip paths to TS using the "zip://" prefix, that TS // doesn't understand. This layer makes sure to remove the protocol @@ -31,7 +33,11 @@ const moduleWrapper = tsserver => { function toEditorPath(str) { // We add the `zip:` prefix to both `.zip/` paths and virtual paths - if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { + if ( + isAbsolute(str) && + !str.match(/^\^?(zip:|\/zip\/)/) && + (str.match(/\.zip\//) || isVirtual(str)) + ) { // We also take the opportunity to turn virtual paths into physical ones; // this makes it much easier to work with workspaces that list peer // dependencies, since otherwise Ctrl+Click would bring us to the virtual @@ -45,7 +51,11 @@ const moduleWrapper = tsserver => { const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; if (resolved) { const locator = pnpApi.findPackageLocator(resolved); - if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { + if ( + locator && + (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || + isPortal(locator.reference)) + ) { str = resolved; } } @@ -73,42 +83,58 @@ const moduleWrapper = tsserver => { // Before | ^/zip/c:/foo/bar.zip/package.json // After | ^/zip//c:/foo/bar.zip/package.json // - case `vscode <1.61`: { - str = `^zip:${str}`; - } break; + case `vscode <1.61`: + { + str = `^zip:${str}`; + } + break; - case `vscode <1.66`: { - str = `^/zip/${str}`; - } break; + case `vscode <1.66`: + { + str = `^/zip/${str}`; + } + break; - case `vscode <1.68`: { - str = `^/zip${str}`; - } break; + case `vscode <1.68`: + { + str = `^/zip${str}`; + } + break; - case `vscode`: { - str = `^/zip/${str}`; - } break; + case `vscode`: + { + str = `^/zip/${str}`; + } + break; // To make "go to definition" work, // We have to resolve the actual file system path from virtual path // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) - case `coc-nvim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = resolve(`zipfile:${str}`); - } break; + case `coc-nvim`: + { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } + break; // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) // We have to resolve the actual file system path from virtual path, // everything else is up to neovim - case `neovim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = `zipfile://${str}`; - } break; + case `neovim`: + { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } + break; - default: { - str = `zip:${str}`; - } break; + default: + { + str = `zip:${str}`; + } + break; } + } else { + str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); } } @@ -117,26 +143,35 @@ const moduleWrapper = tsserver => { function fromEditorPath(str) { switch (hostInfo) { - case `coc-nvim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for coc-nvim is in format of //zipfile://.yarn/... - // So in order to convert it back, we use .* to match all the thing - // before `zipfile:` - return process.platform === `win32` - ? str.replace(/^.*zipfile:\//, ``) - : str.replace(/^.*zipfile:/, ``); - } break; + case `coc-nvim`: + { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } + break; - case `neovim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for neovim is in format of zipfile:////.yarn/... - return str.replace(/^zipfile:\/\//, ``); - } break; + case `neovim`: + { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } + break; case `vscode`: - default: { - return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) - } break; + default: + { + return str.replace( + /^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, + process.platform === `win32` ? `` : `/` + ); + } + break; } } @@ -148,8 +183,9 @@ const moduleWrapper = tsserver => { // TypeScript already does local loads and if this code is running the user trusts the workspace // https://github.com/microsoft/vscode/issues/45856 const ConfiguredProject = tsserver.server.ConfiguredProject; - const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; - ConfiguredProject.prototype.enablePluginsWithOptions = function() { + const { enablePluginsWithOptions: originalEnablePluginsWithOptions } = + ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function () { this.projectService.allowLocalPluginLoads = true; return originalEnablePluginsWithOptions.apply(this, arguments); }; @@ -159,7 +195,8 @@ const moduleWrapper = tsserver => { // like an absolute path of ours and normalize it. const Session = tsserver.server.Session; - const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + const { onMessage: originalOnMessage, send: originalSend } = + Session.prototype; let hostInfo = `unknown`; Object.assign(Session.prototype, { @@ -175,10 +212,12 @@ const moduleWrapper = tsserver => { ) { hostInfo = parsedMessage.arguments.hostInfo; if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { - const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( - // The RegExp from https://semver.org/ but without the caret at the start - /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - ) ?? []).map(Number) + const [, major, minor] = ( + process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + ) ?? [] + ).map(Number); if (major === 1) { if (minor < 61) { @@ -192,21 +231,31 @@ const moduleWrapper = tsserver => { } } - const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { - return typeof value === 'string' ? fromEditorPath(value) : value; - }); + const processedMessageJSON = JSON.stringify( + parsedMessage, + (key, value) => { + return typeof value === 'string' ? fromEditorPath(value) : value; + } + ); return originalOnMessage.call( this, - isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + isStringMessage + ? processedMessageJSON + : JSON.parse(processedMessageJSON) ); }, send(/** @type {any} */ msg) { - return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { - return typeof value === `string` ? toEditorPath(value) : value; - }))); - } + return originalSend.call( + this, + JSON.parse( + JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }) + ) + ); + }, }); return tsserver; diff --git a/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js b/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js index 878b11946a..1fc12fde93 100644 --- a/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js +++ b/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js @@ -1,29 +1,31 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, createRequireFromPath} = require(`module`); -const {resolve} = require(`path`); +const { existsSync } = require(`fs`); +const { createRequire, createRequireFromPath } = require(`module`); +const { resolve } = require(`path`); -const relPnpApiPath = "../../../../.pnp.cjs"; +const relPnpApiPath = '../../../../.pnp.cjs'; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); -const moduleWrapper = tsserver => { +const moduleWrapper = (tsserver) => { if (!process.versions.pnp) { return tsserver; } - const {isAbsolute} = require(`path`); + const { isAbsolute } = require(`path`); const pnpApi = require(`pnpapi`); - const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); - const isPortal = str => str.startsWith("portal:/"); - const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + const isVirtual = (str) => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = (str) => str.startsWith('portal:/'); + const normalize = (str) => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); - const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { - return `${locator.name}@${locator.reference}`; - })); + const dependencyTreeRoots = new Set( + pnpApi.getDependencyTreeRoots().map((locator) => { + return `${locator.name}@${locator.reference}`; + }) + ); // VSCode sends the zip paths to TS using the "zip://" prefix, that TS // doesn't understand. This layer makes sure to remove the protocol @@ -31,7 +33,11 @@ const moduleWrapper = tsserver => { function toEditorPath(str) { // We add the `zip:` prefix to both `.zip/` paths and virtual paths - if (isAbsolute(str) && !str.match(/^\^?(zip:|\/zip\/)/) && (str.match(/\.zip\//) || isVirtual(str))) { + if ( + isAbsolute(str) && + !str.match(/^\^?(zip:|\/zip\/)/) && + (str.match(/\.zip\//) || isVirtual(str)) + ) { // We also take the opportunity to turn virtual paths into physical ones; // this makes it much easier to work with workspaces that list peer // dependencies, since otherwise Ctrl+Click would bring us to the virtual @@ -45,7 +51,11 @@ const moduleWrapper = tsserver => { const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; if (resolved) { const locator = pnpApi.findPackageLocator(resolved); - if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { + if ( + locator && + (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || + isPortal(locator.reference)) + ) { str = resolved; } } @@ -73,42 +83,58 @@ const moduleWrapper = tsserver => { // Before | ^/zip/c:/foo/bar.zip/package.json // After | ^/zip//c:/foo/bar.zip/package.json // - case `vscode <1.61`: { - str = `^zip:${str}`; - } break; + case `vscode <1.61`: + { + str = `^zip:${str}`; + } + break; - case `vscode <1.66`: { - str = `^/zip/${str}`; - } break; + case `vscode <1.66`: + { + str = `^/zip/${str}`; + } + break; - case `vscode <1.68`: { - str = `^/zip${str}`; - } break; + case `vscode <1.68`: + { + str = `^/zip${str}`; + } + break; - case `vscode`: { - str = `^/zip/${str}`; - } break; + case `vscode`: + { + str = `^/zip/${str}`; + } + break; // To make "go to definition" work, // We have to resolve the actual file system path from virtual path // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) - case `coc-nvim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = resolve(`zipfile:${str}`); - } break; + case `coc-nvim`: + { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } + break; // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) // We have to resolve the actual file system path from virtual path, // everything else is up to neovim - case `neovim`: { - str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = `zipfile://${str}`; - } break; + case `neovim`: + { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile://${str}`; + } + break; - default: { - str = `zip:${str}`; - } break; + default: + { + str = `zip:${str}`; + } + break; } + } else { + str = str.replace(/^\/?/, process.platform === `win32` ? `` : `/`); } } @@ -117,26 +143,35 @@ const moduleWrapper = tsserver => { function fromEditorPath(str) { switch (hostInfo) { - case `coc-nvim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for coc-nvim is in format of //zipfile://.yarn/... - // So in order to convert it back, we use .* to match all the thing - // before `zipfile:` - return process.platform === `win32` - ? str.replace(/^.*zipfile:\//, ``) - : str.replace(/^.*zipfile:/, ``); - } break; + case `coc-nvim`: + { + str = str.replace(/\.zip::/, `.zip/`); + // The path for coc-nvim is in format of //zipfile://.yarn/... + // So in order to convert it back, we use .* to match all the thing + // before `zipfile:` + return process.platform === `win32` + ? str.replace(/^.*zipfile:\//, ``) + : str.replace(/^.*zipfile:/, ``); + } + break; - case `neovim`: { - str = str.replace(/\.zip::/, `.zip/`); - // The path for neovim is in format of zipfile:////.yarn/... - return str.replace(/^zipfile:\/\//, ``); - } break; + case `neovim`: + { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } + break; case `vscode`: - default: { - return str.replace(/^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, process.platform === `win32` ? `` : `/`) - } break; + default: + { + return str.replace( + /^\^?(zip:|\/zip(\/ts-nul-authority)?)\/+/, + process.platform === `win32` ? `` : `/` + ); + } + break; } } @@ -148,8 +183,9 @@ const moduleWrapper = tsserver => { // TypeScript already does local loads and if this code is running the user trusts the workspace // https://github.com/microsoft/vscode/issues/45856 const ConfiguredProject = tsserver.server.ConfiguredProject; - const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; - ConfiguredProject.prototype.enablePluginsWithOptions = function() { + const { enablePluginsWithOptions: originalEnablePluginsWithOptions } = + ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function () { this.projectService.allowLocalPluginLoads = true; return originalEnablePluginsWithOptions.apply(this, arguments); }; @@ -159,7 +195,8 @@ const moduleWrapper = tsserver => { // like an absolute path of ours and normalize it. const Session = tsserver.server.Session; - const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + const { onMessage: originalOnMessage, send: originalSend } = + Session.prototype; let hostInfo = `unknown`; Object.assign(Session.prototype, { @@ -175,10 +212,12 @@ const moduleWrapper = tsserver => { ) { hostInfo = parsedMessage.arguments.hostInfo; if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { - const [, major, minor] = (process.env.VSCODE_IPC_HOOK.match( - // The RegExp from https://semver.org/ but without the caret at the start - /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ - ) ?? []).map(Number) + const [, major, minor] = ( + process.env.VSCODE_IPC_HOOK.match( + // The RegExp from https://semver.org/ but without the caret at the start + /(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ + ) ?? [] + ).map(Number); if (major === 1) { if (minor < 61) { @@ -192,21 +231,31 @@ const moduleWrapper = tsserver => { } } - const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { - return typeof value === 'string' ? fromEditorPath(value) : value; - }); + const processedMessageJSON = JSON.stringify( + parsedMessage, + (key, value) => { + return typeof value === 'string' ? fromEditorPath(value) : value; + } + ); return originalOnMessage.call( this, - isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + isStringMessage + ? processedMessageJSON + : JSON.parse(processedMessageJSON) ); }, send(/** @type {any} */ msg) { - return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { - return typeof value === `string` ? toEditorPath(value) : value; - }))); - } + return originalSend.call( + this, + JSON.parse( + JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }) + ) + ); + }, }); return tsserver; diff --git a/tgui/.yarn/sdks/typescript/lib/typescript.js b/tgui/.yarn/sdks/typescript/lib/typescript.js index cbdbf1500f..384f448c1f 100644 --- a/tgui/.yarn/sdks/typescript/lib/typescript.js +++ b/tgui/.yarn/sdks/typescript/lib/typescript.js @@ -1,20 +1,20 @@ #!/usr/bin/env node -const {existsSync} = require(`fs`); -const {createRequire, createRequireFromPath} = require(`module`); -const {resolve} = require(`path`); +const { existsSync } = require(`fs`); +const { createRequire, createRequireFromPath } = require(`module`); +const { resolve } = require(`path`); -const relPnpApiPath = "../../../../.pnp.cjs"; +const relPnpApiPath = '../../../../.pnp.cjs'; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); if (existsSync(absPnpApiPath)) { if (!process.versions.pnp) { - // Setup the environment to be able to require typescript/lib/typescript.js + // Setup the environment to be able to require typescript require(absPnpApiPath).setup(); } } -// Defer to the real typescript/lib/typescript.js your application uses -module.exports = absRequire(`typescript/lib/typescript.js`); +// Defer to the real typescript your application uses +module.exports = absRequire(`typescript`); diff --git a/tgui/.yarn/sdks/typescript/package.json b/tgui/.yarn/sdks/typescript/package.json index ea85e133e8..9d77f1ac3a 100644 --- a/tgui/.yarn/sdks/typescript/package.json +++ b/tgui/.yarn/sdks/typescript/package.json @@ -2,5 +2,9 @@ "name": "typescript", "version": "4.3.5-sdk", "main": "./lib/typescript.js", - "type": "commonjs" + "type": "commonjs", + "bin": { + "tsc": "./bin/tsc", + "tsserver": "./bin/tsserver" + } } diff --git a/tgui/.yarnrc.yml b/tgui/.yarnrc.yml index b6387e8e46..086484a243 100644 --- a/tgui/.yarnrc.yml +++ b/tgui/.yarnrc.yml @@ -8,7 +8,7 @@ logFilters: plugins: - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs - spec: "@yarnpkg/plugin-interactive-tools" + spec: '@yarnpkg/plugin-interactive-tools' pnpEnableEsmLoader: false diff --git a/tgui/README.md b/tgui/README.md index c0ff51f2e9..ad79758279 100644 --- a/tgui/README.md +++ b/tgui/README.md @@ -22,12 +22,9 @@ start with our [practical tutorial](docs/tutorial-and-examples.md). ### Guides -This project uses **Inferno** - a very fast UI rendering engine with a similar -API to React. Take your time to read these guides: +This project uses React. Take your time to read the guide: -- [React guide](https://reactjs.org/docs/hello-world.html) -- [Inferno documentation](https://infernojs.org/docs/guides/components) - -highlights differences with React. +- [React guide](https://react.dev/learn) If you were already familiar with an older, Ractive-based tgui, and want to translate concepts between old and new tgui, read this @@ -164,7 +161,7 @@ Press `F12` to open the KitchenSink interface. This interface is a playground to test various tgui components. **Layout Debugger.** -Press `F11` to toggle the *layout debugger*. It will show outlines of +Press `F11` to toggle the _layout debugger_. It will show outlines of all tgui elements, which makes it easy to understand how everything comes together, and can reveal certain layout bugs which are not normally visible. diff --git a/tgui/babel.config.js b/tgui/babel.config.js deleted file mode 100644 index e702c9a711..0000000000 --- a/tgui/babel.config.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -const createBabelConfig = (options) => { - const { presets = [], plugins = [], removeConsole } = options; - // prettier-ignore - return { - presets: [ - [require.resolve('@babel/preset-typescript'), { - allowDeclareFields: true, - }], - [require.resolve('@babel/preset-env'), { - modules: 'commonjs', - useBuiltIns: 'entry', - corejs: '3', - spec: false, - loose: true, - targets: [], - }], - ...presets, - ].filter(Boolean), - plugins: [ - [require.resolve('@babel/plugin-proposal-class-properties'), { - loose: true, - }], - require.resolve('@babel/plugin-transform-jscript'), - require.resolve('babel-plugin-inferno'), - removeConsole && require.resolve('babel-plugin-transform-remove-console'), - require.resolve('common/string.babel-plugin.cjs'), - ...plugins, - ].filter(Boolean), - }; -}; - -module.exports = (api) => { - api.cache(true); - const mode = process.env.NODE_ENV; - return createBabelConfig({ mode }); -}; - -module.exports.createBabelConfig = createBabelConfig; diff --git a/tgui/bin/tgui_.ps1 b/tgui/bin/tgui_.ps1 index b451ac0672..eb9c7a1a97 100644 --- a/tgui/bin/tgui_.ps1 +++ b/tgui/bin/tgui_.ps1 @@ -59,7 +59,7 @@ function task-prettier { } function task-prettify { - yarn prettierx --write packages @Args + yarn prettier --write packages @Args } ## Run a linter through all packages diff --git a/tgui/docs/component-reference.md b/tgui/docs/component-reference.md index 05ec6ef56f..98e03d1be6 100644 --- a/tgui/docs/component-reference.md +++ b/tgui/docs/component-reference.md @@ -65,19 +65,13 @@ it is used a lot in this framework. **Event handlers.** Event handlers are callbacks that you can attack to various element to -listen for browser events. Inferno supports camelcase (`onClick`) and -lowercase (`onclick`) event names. +listen for browser events. React supports camelcase (`onClick`) event names. - Camel case names are what's called *synthetic* events, and are the **preferred way** of handling events in React, for efficiency and performance reasons. Please read -[Inferno Event Handling](https://infernojs.org/docs/guides/event-handling) +[React Event Handling](https://react.dev/learn/responding-to-events) to understand what this is about. -- Lower case names are native browser events and should be used sparingly, -for example when you need an explicit IE8 support. **DO NOT** use -lowercase event handlers unless you really know what you are doing. -- [Button](#button) component does not support the lowercase `onclick` event. -Use the camel case `onClick` instead. ## `tgui/components` @@ -746,9 +740,10 @@ Popper lets you position elements so that they don't go out of the bounds of the **Props:** -- `popperContent: InfernoNode` - The content that will be put inside the popper. -- `options?: { ... }` - An object of options to pass to `createPopper`. See [https://popper.js.org/docs/v2/constructors/#options], but the one you want most is `placement`. Valid placements are "bottom", "top", "left", and "right". You can affix "-start" and "-end" to achieve something like top left or top right respectively. You can also use "auto" (with an optional "-start" or "-end"), where a best fit will be chosen. -- `additionalStyles: { ... }` - A map of CSS styles to add to the element that will contain the popper. +- `content: ReactNode` - The content that will be put inside the popper. +- `isOpen: boolean` - Whether or not the popper is open. +- `onClickOutside?: (e) => void` - A function that will be called when the user clicks outside of the popper. +- `placement?: string` - The placement of the popper. See [https://popper.js.org/docs/v2/constructors/#placement] ### `ProgressBar` diff --git a/tgui/docs/state-usage.md b/tgui/docs/state-usage.md new file mode 100644 index 0000000000..9d3a2812a6 --- /dev/null +++ b/tgui/docs/state-usage.md @@ -0,0 +1,30 @@ +# 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 +``` diff --git a/tgui/jest.config.js b/tgui/jest.config.js index 8b78818004..d8b4ac3e41 100644 --- a/tgui/jest.config.js +++ b/tgui/jest.config.js @@ -8,7 +8,7 @@ module.exports = { testEnvironment: 'jsdom', testRunner: require.resolve('jest-circus/runner'), transform: { - '^.+\\.(js|cjs|ts|tsx)$': require.resolve('babel-jest'), + '^.+\\.(js|cjs|ts|tsx)$': require.resolve('@swc/jest'), }, moduleFileExtensions: ['js', 'cjs', 'ts', 'tsx', 'json'], resetMocks: true, diff --git a/tgui/package.json b/tgui/package.json index a3bdcb4d90..bfbc5a34ab 100644 --- a/tgui/package.json +++ b/tgui/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "tgui-workspace", - "version": "4.3.1", + "version": "5.0.0", "packageManager": "yarn@3.3.1", "workspaces": [ "packages/*" @@ -9,54 +9,50 @@ "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" + "tgui:tsc": "tsc", + "tgui:prettier-fix": "prettier --write .", + "tgui:eslint-fix": "eslint --fix packages --ext .js,.cjs,.ts,.tsx" }, "dependencies": { - "@babel/core": "^7.18.0", - "@babel/eslint-parser": "^7.17.0", - "@babel/plugin-proposal-class-properties": "^7.17.12", - "@babel/plugin-transform-jscript": "^7.17.12", - "@babel/preset-env": "^7.18.0", - "@babel/preset-typescript": "^7.17.12", - "@types/jest": "^27.5.1", - "@types/jsdom": "^16.2.14", + "@swc/core": "^1.3.100", + "@swc/jest": "^0.2.29", + "@types/jest": "^29.5.10", + "@types/jsdom": "^21.1.6", "@types/node": "^14.x", - "@types/webpack": "^5.28.0", - "@types/webpack-env": "^1.17.0", - "@typescript-eslint/parser": "^5.25.0", - "babel-jest": "^28.1.0", - "babel-loader": "^8.2.5", - "babel-plugin-inferno": "^6.4.0", - "babel-plugin-transform-remove-console": "^6.9.4", - "common": "workspace:*", - "css-loader": "^6.7.1", + "@types/webpack": "^5.28.5", + "@types/webpack-env": "^1.18.4", + "@typescript-eslint/parser": "^6.14.0", + "css-loader": "^6.8.1", "esbuild-loader": "^4.0.2", - "eslint": "^8.16.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-radar": "^0.2.1", - "eslint-plugin-react": "^7.30.0", - "eslint-plugin-unused-imports": "^2.0.0", - "inferno": "^7.4.11", - "jest": "^28.1.0", - "jest-circus": "^28.1.0", - "jest-environment-jsdom": "^28.1.0", - "jsdom": "^19.0.0", - "mini-css-extract-plugin": "^2.6.0", - "prettier": "npm:prettierx@0.19.0", - "sass": "^1.52.1", - "sass-loader": "^13.0.0", - "style-loader": "^3.3.1", - "typescript": "^4.6.4", - "webpack": "^5.76.0", - "webpack-bundle-analyzer": "^4.5.0", - "webpack-cli": "^4.9.2" + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-simple-import-sort": "^10.0.0", + "eslint-plugin-sonarjs": "^0.23.0", + "eslint-plugin-unused-imports": "^3.0.0", + "file-loader": "^6.2.0", + "jest": "^29.7.0", + "jest-circus": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jsdom": "^22.1.0", + "mini-css-extract-plugin": "^2.7.6", + "prettier": "^3.1.0", + "sass": "^1.69.5", + "sass-loader": "^13.3.2", + "style-loader": "^3.3.3", + "swc-loader": "^0.2.3", + "typescript": "^4.9.4", + "url-loader": "^4.1.1", + "webpack": "^5.89.0", + "webpack-bundle-analyzer": "^4.10.1", + "webpack-cli": "^5.1.4" } } diff --git a/tgui/packages/common/collections.ts b/tgui/packages/common/collections.ts index a005da7aa1..5bfcee8588 100644 --- a/tgui/packages/common/collections.ts +++ b/tgui/packages/common/collections.ts @@ -32,12 +32,12 @@ export const filter = }; type MapFunction = { - (iterateeFn: (value: T, index: number, collection: T[]) => U): ( - collection: T[] - ) => U[]; + ( + iterateeFn: (value: T, index: number, collection: T[]) => U, + ): (collection: T[]) => U[]; ( - iterateeFn: (value: T, index: K, collection: Record) => U + iterateeFn: (value: T, index: K, collection: Record) => U, ): (collection: Record) => U[]; }; @@ -75,7 +75,7 @@ export const map: MapFunction = */ export const filterMap = ( collection: T[], - iterateeFn: (value: T) => U | undefined + iterateeFn: (value: T) => U | undefined, ): U[] => { const finalCollection: U[] = []; @@ -261,7 +261,7 @@ export const zipWith = const binarySearch = ( getKey: (value: T) => U, collection: readonly T[], - inserting: T + inserting: T, ): number => { if (collection.length === 0) { return 0; diff --git a/tgui/packages/common/color.js b/tgui/packages/common/color.js index b59d82247a..59935931d8 100644 --- a/tgui/packages/common/color.js +++ b/tgui/packages/common/color.js @@ -30,7 +30,7 @@ export class Color { this.r - this.r * percent, this.g - this.g * percent, this.b - this.b * percent, - this.a + this.a, ); } @@ -48,7 +48,7 @@ Color.fromHex = (hex) => new Color( parseInt(hex.substr(1, 2), 16), parseInt(hex.substr(3, 2), 16), - parseInt(hex.substr(5, 2), 16) + parseInt(hex.substr(5, 2), 16), ); /** @@ -59,7 +59,7 @@ Color.lerp = (c1, c2, n) => (c2.r - c1.r) * n + c1.r, (c2.g - c1.g) * n + c1.g, (c2.b - c1.b) * n + c1.b, - (c2.a - c1.a) * n + c1.a + (c2.a - c1.a) * n + c1.a, ); /** diff --git a/tgui/packages/common/keys.ts b/tgui/packages/common/keys.ts index 61b79992b4..34ac9e1614 100644 --- a/tgui/packages/common/keys.ts +++ b/tgui/packages/common/keys.ts @@ -22,18 +22,18 @@ export enum KEY { Backspace = 'Backspace', Control = 'Control', Delete = 'Delete', - Down = 'Down', + Down = 'ArrowDown', End = 'End', Enter = 'Enter', - Escape = 'Esc', + Escape = 'Escape', Home = 'Home', Insert = 'Insert', - Left = 'Left', + Left = 'ArrowLeft', PageDown = 'PageDown', PageUp = 'PageUp', - Right = 'Right', + Right = 'ArrowRight', Shift = 'Shift', Space = ' ', Tab = 'Tab', - Up = 'Up', + Up = 'ArrowUp', } diff --git a/tgui/packages/common/react.ts b/tgui/packages/common/react.ts index 8e42d0971a..5260ff6ae1 100644 --- a/tgui/packages/common/react.ts +++ b/tgui/packages/common/react.ts @@ -52,13 +52,10 @@ export const shallowDiffers = (a: object, b: object) => { }; /** - * Default inferno hooks for pure components. + * A common case in tgui, when you pass a value conditionally, these are + * the types that can fall through the condition. */ -export const pureComponentHooks = { - onComponentShouldUpdate: (lastProps, nextProps) => { - return shallowDiffers(lastProps, nextProps); - }, -}; +export type BooleanLike = number | boolean | null | undefined; /** * A helper to determine whether the object is renderable by React. @@ -69,9 +66,3 @@ export const canRender = (value: unknown) => { && value !== null && typeof value !== 'boolean'; }; - -/** - * A common case in tgui, when you pass a value conditionally, these are - * the types that can fall through the condition. - */ -export type BooleanLike = number | boolean | null | undefined; diff --git a/tgui/packages/common/redux.test.ts b/tgui/packages/common/redux.test.ts index af4e5d4e73..d4af99907c 100644 --- a/tgui/packages/common/redux.test.ts +++ b/tgui/packages/common/redux.test.ts @@ -1,4 +1,11 @@ -import { Action, Reducer, applyMiddleware, combineReducers, createAction, createStore } from './redux'; +import { + Action, + applyMiddleware, + combineReducers, + createAction, + createStore, + Reducer, +} from './redux'; // Dummy Reducer const counterReducer: Reducer> = (state = 0, action) => { @@ -31,7 +38,7 @@ describe('Redux implementation tests', () => { test('createStore with applyMiddleware works', () => { const store = createStore( counterReducer, - applyMiddleware(loggingMiddleware) + applyMiddleware(loggingMiddleware), ); expect(store.getState()).toBe(0); }); diff --git a/tgui/packages/common/redux.ts b/tgui/packages/common/redux.ts index 1635853c6b..c8eb268f5d 100644 --- a/tgui/packages/common/redux.ts +++ b/tgui/packages/common/redux.ts @@ -6,7 +6,7 @@ export type Reducer = ( state: State | undefined, - action: ActionType + action: ActionType, ) => State; export type Store = { @@ -21,7 +21,7 @@ type MiddlewareAPI = { }; export type Middleware = ( - storeApi: MiddlewareAPI + storeApi: MiddlewareAPI, ) => (next: Dispatch) => Dispatch; export type Action = { @@ -33,7 +33,7 @@ export type AnyAction = Action & { }; export type Dispatch = ( - action: ActionType + action: ActionType, ) => void; type StoreEnhancer = (createStoreFunction: Function) => Function; @@ -48,7 +48,7 @@ type PreparedAction = { */ export const createStore = ( reducer: Reducer, - enhancer?: StoreEnhancer + enhancer?: StoreEnhancer, ): Store => { // Apply a store enhancer (applyMiddleware is one of them). if (enhancer) { @@ -90,14 +90,14 @@ export const applyMiddleware = ( ...middlewares: Middleware[] ): StoreEnhancer => { return ( - createStoreFunction: (reducer: Reducer, enhancer?: StoreEnhancer) => Store + createStoreFunction: (reducer: Reducer, enhancer?: StoreEnhancer) => Store, ) => { return (reducer, ...args): Store => { const store = createStoreFunction(reducer, ...args); - let dispatch: Dispatch = () => { + let dispatch: Dispatch = (action, ...args) => { throw new Error( - 'Dispatching while constructing your middleware is not allowed.' + 'Dispatching while constructing your middleware is not allowed.', ); }; @@ -109,7 +109,7 @@ export const applyMiddleware = ( const chain = middlewares.map((middleware) => middleware(storeApi)); dispatch = chain.reduceRight( (next, middleware) => middleware(next), - store.dispatch + store.dispatch, ); return { @@ -129,7 +129,7 @@ export const applyMiddleware = ( * is also more flexible than the redux counterpart. */ export const combineReducers = ( - reducersObj: Record + reducersObj: Record, ): Reducer => { const keys = Object.keys(reducersObj); @@ -170,7 +170,7 @@ export const combineReducers = ( */ export const createAction = ( type: TAction, - prepare?: (...args: any[]) => PreparedAction + prepare?: (...args: any[]) => PreparedAction, ) => { const actionCreator = (...args: any[]) => { let action: Action & PreparedAction = { type }; @@ -194,23 +194,3 @@ export const createAction = ( return actionCreator; }; - -// Implementation specific -// -------------------------------------------------------- - -export const useDispatch = (context: { - store: Store; -}): Dispatch => { - return context?.store?.dispatch; -}; - -export const useSelector = ( - context: { store: Store }, - selector: (state: State) => Selected -): Selected => { - if (!context) { - return {} as Selected; - } - - return selector(context?.store?.getState()); -}; diff --git a/tgui/packages/common/timer.ts b/tgui/packages/common/timer.ts index 49d3648420..1fc3e11fd3 100644 --- a/tgui/packages/common/timer.ts +++ b/tgui/packages/common/timer.ts @@ -13,7 +13,7 @@ export const debounce = any>( fn: F, time: number, - immediate = false + immediate = false, ): ((...args: Parameters) => void) => { let timeout: ReturnType | null; return (...args: Parameters) => { @@ -38,7 +38,7 @@ export const debounce = any>( */ export const throttle = any>( fn: F, - time: number + time: number, ): ((...args: Parameters) => void) => { let previouslyRun: number | null, queuedToRun: ReturnType | null; @@ -53,7 +53,7 @@ export const throttle = any>( } else { queuedToRun = setTimeout( () => invokeFn(...args), - time - (now - (previouslyRun ?? 0)) + time - (now - (previouslyRun ?? 0)), ); } }; diff --git a/tgui/packages/tgfont/dist/tgfont.css b/tgui/packages/tgfont/dist/tgfont.css index 7e75af2f05..568f178c2e 100644 --- a/tgui/packages/tgfont/dist/tgfont.css +++ b/tgui/packages/tgfont/dist/tgfont.css @@ -1,6 +1,7 @@ @font-face { font-family: 'tgfont'; - src: url('./tgfont.woff2?958b912b123580c55529f68c4e2261bd') format('woff2'), + src: + url('./tgfont.woff2?958b912b123580c55529f68c4e2261bd') format('woff2'), url('./tgfont.eot?958b912b123580c55529f68c4e2261bd#iefix') format('embedded-opentype'); } diff --git a/tgui/packages/tgui-bench/entrypoint.tsx b/tgui/packages/tgui-bench/entrypoint.tsx index a6eb7a91d6..b160d320b5 100644 --- a/tgui/packages/tgui-bench/entrypoint.tsx +++ b/tgui/packages/tgui-bench/entrypoint.tsx @@ -4,8 +4,10 @@ * @license MIT */ -import { setupGlobalEvents } from 'tgui/events'; import 'tgui/styles/main.scss'; + +import { setupGlobalEvents } from 'tgui/events'; + import Benchmark from './lib/benchmark'; const sendMessage = (obj: any) => { diff --git a/tgui/packages/tgui-bench/lib/benchmark.d.ts b/tgui/packages/tgui-bench/lib/benchmark.d.ts index 7f3310005f..3eac568d41 100644 --- a/tgui/packages/tgui-bench/lib/benchmark.d.ts +++ b/tgui/packages/tgui-bench/lib/benchmark.d.ts @@ -27,7 +27,7 @@ declare class Benchmark { static reduce( arr: T[], callback: (accumulator: K, value: T) => K, - thisArg?: any + thisArg?: any, ): K; static options: Benchmark.Options; diff --git a/tgui/packages/tgui-bench/lib/benchmark.js b/tgui/packages/tgui-bench/lib/benchmark.js index 33a645f294..5cb8b15f59 100644 --- a/tgui/packages/tgui-bench/lib/benchmark.js +++ b/tgui/packages/tgui-bench/lib/benchmark.js @@ -15,8 +15,8 @@ module.exports = function () { /** Used to determine if values are of the language type Object. */ var objectTypes = { - 'function': true, - 'object': true, + function: true, + object: true, }; /** Used as a reference to the global object. */ @@ -58,11 +58,11 @@ module.exports = function () { /** Used to avoid hz of Infinity. */ var divisors = { - '1': 4096, - '2': 512, - '3': 64, - '4': 8, - '5': 0, + 1: 4096, + 2: 512, + 3: 64, + 4: 8, + 5: 0, }; /** @@ -70,37 +70,37 @@ module.exports = function () { * For more info see http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm. */ var tTable = { - '1': 12.706, - '2': 4.303, - '3': 3.182, - '4': 2.776, - '5': 2.571, - '6': 2.447, - '7': 2.365, - '8': 2.306, - '9': 2.262, - '10': 2.228, - '11': 2.201, - '12': 2.179, - '13': 2.16, - '14': 2.145, - '15': 2.131, - '16': 2.12, - '17': 2.11, - '18': 2.101, - '19': 2.093, - '20': 2.086, - '21': 2.08, - '22': 2.074, - '23': 2.069, - '24': 2.064, - '25': 2.06, - '26': 2.056, - '27': 2.052, - '28': 2.048, - '29': 2.045, - '30': 2.042, - 'infinity': 1.96, + 1: 12.706, + 2: 4.303, + 3: 3.182, + 4: 2.776, + 5: 2.571, + 6: 2.447, + 7: 2.365, + 8: 2.306, + 9: 2.262, + 10: 2.228, + 11: 2.201, + 12: 2.179, + 13: 2.16, + 14: 2.145, + 15: 2.131, + 16: 2.12, + 17: 2.11, + 18: 2.101, + 19: 2.093, + 20: 2.086, + 21: 2.08, + 22: 2.074, + 23: 2.069, + 24: 2.064, + 25: 2.06, + 26: 2.056, + 27: 2.052, + 28: 2.048, + 29: 2.045, + 30: 2.042, + infinity: 1.96, }; /** @@ -108,61 +108,61 @@ module.exports = function () { * For more info see http://www.saburchill.com/IBbiology/stats/003.html. */ var uTable = { - '5': [0, 1, 2], - '6': [1, 2, 3, 5], - '7': [1, 3, 5, 6, 8], - '8': [2, 4, 6, 8, 10, 13], - '9': [2, 4, 7, 10, 12, 15, 17], - '10': [3, 5, 8, 11, 14, 17, 20, 23], - '11': [3, 6, 9, 13, 16, 19, 23, 26, 30], - '12': [4, 7, 11, 14, 18, 22, 26, 29, 33, 37], - '13': [4, 8, 12, 16, 20, 24, 28, 33, 37, 41, 45], - '14': [5, 9, 13, 17, 22, 26, 31, 36, 40, 45, 50, 55], - '15': [5, 10, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64], - '16': [6, 11, 15, 21, 26, 31, 37, 42, 47, 53, 59, 64, 70, 75], - '17': [6, 11, 17, 22, 28, 34, 39, 45, 51, 57, 63, 67, 75, 81, 87], - '18': [7, 12, 18, 24, 30, 36, 42, 48, 55, 61, 67, 74, 80, 86, 93, 99], - '19': [7, 13, 19, 25, 32, 38, 45, 52, 58, 65, 72, 78, 85, 92, 99, 106, 113], - '20': [ + 5: [0, 1, 2], + 6: [1, 2, 3, 5], + 7: [1, 3, 5, 6, 8], + 8: [2, 4, 6, 8, 10, 13], + 9: [2, 4, 7, 10, 12, 15, 17], + 10: [3, 5, 8, 11, 14, 17, 20, 23], + 11: [3, 6, 9, 13, 16, 19, 23, 26, 30], + 12: [4, 7, 11, 14, 18, 22, 26, 29, 33, 37], + 13: [4, 8, 12, 16, 20, 24, 28, 33, 37, 41, 45], + 14: [5, 9, 13, 17, 22, 26, 31, 36, 40, 45, 50, 55], + 15: [5, 10, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64], + 16: [6, 11, 15, 21, 26, 31, 37, 42, 47, 53, 59, 64, 70, 75], + 17: [6, 11, 17, 22, 28, 34, 39, 45, 51, 57, 63, 67, 75, 81, 87], + 18: [7, 12, 18, 24, 30, 36, 42, 48, 55, 61, 67, 74, 80, 86, 93, 99], + 19: [7, 13, 19, 25, 32, 38, 45, 52, 58, 65, 72, 78, 85, 92, 99, 106, 113], + 20: [ 8, 14, 20, 27, 34, 41, 48, 55, 62, 69, 76, 83, 90, 98, 105, 112, 119, 127, ], - '21': [ + 21: [ 8, 15, 22, 29, 36, 43, 50, 58, 65, 73, 80, 88, 96, 103, 111, 119, 126, 134, 142, ], - '22': [ + 22: [ 9, 16, 23, 30, 38, 45, 53, 61, 69, 77, 85, 93, 101, 109, 117, 125, 133, 141, 150, 158, ], - '23': [ + 23: [ 9, 17, 24, 32, 40, 48, 56, 64, 73, 81, 89, 98, 106, 115, 123, 132, 140, 149, 157, 166, 175, ], - '24': [ + 24: [ 10, 17, 25, 33, 42, 50, 59, 67, 76, 85, 94, 102, 111, 120, 129, 138, 147, 156, 165, 174, 183, 192, ], - '25': [ + 25: [ 10, 18, 27, 35, 44, 53, 62, 71, 80, 89, 98, 107, 117, 126, 135, 145, 154, 163, 173, 182, 192, 201, 211, ], - '26': [ + 26: [ 11, 19, 28, 37, 46, 55, 64, 74, 83, 93, 102, 112, 122, 132, 141, 151, 161, 171, 181, 191, 200, 210, 220, 230, ], - '27': [ + 27: [ 11, 20, 29, 38, 48, 57, 67, 77, 87, 97, 107, 118, 125, 138, 147, 158, 168, 178, 188, 199, 209, 219, 230, 240, 250, ], - '28': [ + 28: [ 12, 21, 30, 40, 50, 60, 70, 80, 90, 101, 111, 122, 132, 143, 154, 164, 175, 186, 196, 207, 218, 228, 239, 250, 261, 272, ], - '29': [ + 29: [ 13, 22, 32, 42, 52, 62, 73, 83, 94, 105, 116, 127, 138, 149, 160, 171, 182, 193, 204, 215, 226, 238, 249, 260, 271, 282, 294, ], - '30': [ + 30: [ 13, 23, 33, 43, 54, 65, 76, 87, 98, 109, 120, 131, 143, 154, 166, 177, 189, 200, 212, 223, 235, 247, 258, 270, 282, 293, 305, 317, ], @@ -285,12 +285,12 @@ module.exports = function () { ( 'return (' + function (x) { - return { 'x': '' + (1 + x) + '', 'y': 0 }; + return { x: '' + (1 + x) + '', y: 0 }; } + ')' ) // Avoid issues with code added by Istanbul. - .replace(/__cov__[^;]+;/g, '') + .replace(/__cov__[^;]+;/g, ''), )()(0).x === '1'; } catch (e) { support.decompilation = false; @@ -311,7 +311,7 @@ module.exports = function () { * @memberOf timer * @type {Function|Object} */ - 'ns': Date, + ns: Date, /** * Starts the deferred timer. @@ -320,7 +320,7 @@ module.exports = function () { * @memberOf timer * @param {Object} deferred The deferred instance. */ - 'start': null, // Lazy defined in `clock()`. + start: null, // Lazy defined in `clock()`. /** * Stops the deferred timer. @@ -329,7 +329,7 @@ module.exports = function () { * @memberOf timer * @param {Object} deferred The deferred instance. */ - 'stop': null, // Lazy defined in `clock()`. + stop: null, // Lazy defined in `clock()`. }; /*------------------------------------------------------------------------*/ @@ -478,10 +478,10 @@ module.exports = function () { } return event instanceof Event ? _.assign( - event, - { 'timeStamp': _.now() }, - typeof type == 'string' ? { 'type': type } : type - ) + event, + { timeStamp: _.now() }, + typeof type == 'string' ? { type: type } : type, + ) : new Event(type); } @@ -584,7 +584,7 @@ module.exports = function () { args + '){' + body + - '}' + '}', ); result = anchor[prop]; delete anchor[prop]; @@ -672,7 +672,7 @@ module.exports = function () { // Detect strings containing only the "use strict" directive. return /^(?:\/\*+[\w\W]*?\*\/|\/\/.*?[\n\r\u2028\u2029]|\s)*(["'])use strict\1;?$/.test( - result + result, ) ? '' : result; @@ -772,7 +772,7 @@ module.exports = function () { options = object.options = _.assign( {}, cloneDeep(object.constructor.options), - cloneDeep(options) + cloneDeep(options), ); _.forOwn(options, function (value, key) { @@ -927,11 +927,11 @@ module.exports = function () { bench, queued, index = -1, - eventProps = { 'currentTarget': benches }, + eventProps = { currentTarget: benches }, options = { - 'onStart': _.noop, - 'onCycle': _.noop, - 'onComplete': _.noop, + onStart: _.noop, + onCycle: _.noop, + onComplete: _.noop, }, result = _.toArray(benches); @@ -1167,7 +1167,7 @@ module.exports = function () { function add(name, fn, options) { var suite = this, bench = new Benchmark(name, fn, options), - event = Event({ 'type': 'add', 'target': bench }); + event = Event({ type: 'add', target: bench }); if ((suite.emit(event), !event.cancelled)) { suite.push(bench); @@ -1269,21 +1269,21 @@ module.exports = function () { options || (options = {}); invoke(suite, { - 'name': 'run', - 'args': options, - 'queued': options.queued, - 'onStart': function (event) { + name: 'run', + args: options, + queued: options.queued, + onStart: function (event) { suite.emit(event); }, - 'onCycle': function (event) { + onCycle: function (event) { var bench = event.target; if (bench.error) { - suite.emit({ 'type': 'error', 'target': bench }); + suite.emit({ type: 'error', target: bench }); } suite.emit(event); event.aborted = suite.aborted; }, - 'onComplete': function (event) { + onComplete: function (event) { suite.running = false; suite.emit(event); }, @@ -1415,7 +1415,7 @@ module.exports = function () { _.each(type.split(' '), function (type) { (_.has(events, type) ? events[type] : (events[type] = [])).push( - listener + listener, ); }); return object; @@ -1476,7 +1476,7 @@ module.exports = function () { result.options = _.assign( {}, cloneDeep(bench.options), - cloneDeep(options) + cloneDeep(options), ); // Copy own custom properties. @@ -1521,7 +1521,7 @@ module.exports = function () { function (total, xB) { return total + (xB > xA ? 0 : xB < xA ? 1 : 0.5); }, - 0 + 0, ); } @@ -1531,7 +1531,7 @@ module.exports = function () { function (total, xA) { return total + getScore(xA, sampleB); }, - 0 + 0, ); } @@ -1577,11 +1577,11 @@ module.exports = function () { // A non-recursive solution to check if properties have changed. // For more information see http://www.jslab.dk/articles/non.recursive.preorder.traversal.part4. var data = { - 'destination': bench, - 'source': _.assign( + destination: bench, + source: _.assign( {}, cloneDeep(bench.constructor.prototype), - cloneDeep(bench.options) + cloneDeep(bench.options), ), }; @@ -1615,12 +1615,12 @@ module.exports = function () { // Register a changed object. if (changed) { changes.push({ - 'destination': destination, - 'key': key, - 'value': currValue, + destination: destination, + key: key, + value: currValue, }); } - queue.push({ 'destination': currValue, 'source': value }); + queue.push({ destination: currValue, source: value }); } // Register a changed primitive. else if ( @@ -1628,9 +1628,9 @@ module.exports = function () { !(value == null || _.isFunction(value)) ) { changes.push({ - 'destination': destination, - 'key': key, - 'value': value, + destination: destination, + key: key, + value: value, }); } }); @@ -1674,7 +1674,7 @@ module.exports = function () { } else { // Error#name and Error#message properties are non-enumerable. errorStr = join( - _.assign({ 'name': error.name, 'message': error.message }, error) + _.assign({ name: error.name, message: error.message }, error), ); } result += ': ' + errorStr; @@ -1706,9 +1706,7 @@ module.exports = function () { function clock() { var options = Benchmark.options, templateData = {}, - timers = [ - { 'ns': timer.ns, 'res': max(0.0015, getRes('ms')), 'unit': 'ms' }, - ]; + timers = [{ ns: timer.ns, res: max(0.0015, getRes('ms')), unit: 'ms' }]; // Lazy define for hi-res timers. clock = function (clone) { @@ -1740,20 +1738,20 @@ module.exports = function () { // to avoid potential engine optimizations enabled over the life of the test. var funcBody = deferred ? 'var d#=this,${fnArg}=d#,m#=d#.benchmark._original,f#=m#.fn,su#=m#.setup,td#=m#.teardown;' + - // When `deferred.cycles` is `0` then... - 'if(!d#.cycles){' + - // set `deferred.fn`, - 'd#.fn=function(){var ${fnArg}=d#;if(typeof f#=="function"){try{${fn}\n}catch(e#){f#(d#)}}else{${fn}\n}};' + - // set `deferred.teardown`, - 'd#.teardown=function(){d#.cycles=0;if(typeof td#=="function"){try{${teardown}\n}catch(e#){td#()}}else{${teardown}\n}};' + - // execute the benchmark's `setup`, - 'if(typeof su#=="function"){try{${setup}\n}catch(e#){su#()}}else{${setup}\n};' + - // start timer, - 't#.start(d#);' + - // and then execute `deferred.fn` and return a dummy object. - '}d#.fn();return{uid:"${uid}"}' + // When `deferred.cycles` is `0` then... + 'if(!d#.cycles){' + + // set `deferred.fn`, + 'd#.fn=function(){var ${fnArg}=d#;if(typeof f#=="function"){try{${fn}\n}catch(e#){f#(d#)}}else{${fn}\n}};' + + // set `deferred.teardown`, + 'd#.teardown=function(){d#.cycles=0;if(typeof td#=="function"){try{${teardown}\n}catch(e#){td#()}}else{${teardown}\n}};' + + // execute the benchmark's `setup`, + 'if(typeof su#=="function"){try{${setup}\n}catch(e#){su#()}}else{${setup}\n};' + + // start timer, + 't#.start(d#);' + + // and then execute `deferred.fn` and return a dummy object. + '}d#.fn();return{uid:"${uid}"}' : 'var r#,s#,m#=this,f#=m#.fn,i#=m#.count,n#=t#.ns;${setup}\n${begin};' + - 'while(i#--){${fn}\n}${end};${teardown}\nreturn{elapsed:r#,uid:"${uid}"}'; + 'while(i#--){${fn}\n}${end};${teardown}\nreturn{elapsed:r#,uid:"${uid}"}'; var compiled = (bench.compiled = @@ -1768,7 +1766,7 @@ module.exports = function () { throw new Error( 'The test "' + name + - '" is empty. This may be the result of dead code removal.' + '" is empty. This may be the result of dead code removal.', ); } else if (!deferred) { // Pretest to determine if compiled code exits early, usually by a @@ -1833,14 +1831,14 @@ module.exports = function () { templateData.uid = uid + uidCounter++; _.assign(templateData, { - 'setup': decompilable + setup: decompilable ? getSource(bench.setup) : interpolate('m#.setup()'), - 'fn': decompilable + fn: decompilable ? getSource(fn) : interpolate('m#.fn(' + fnArg + ')'), - 'fnArg': fnArg, - 'teardown': decompilable + fnArg: fnArg, + teardown: decompilable ? getSource(bench.teardown) : interpolate('m#.teardown()'), }); @@ -1848,48 +1846,48 @@ module.exports = function () { // Use API of chosen timer. if (timer.unit == 'ns') { _.assign(templateData, { - 'begin': interpolate('s#=n#()'), - 'end': interpolate('r#=n#(s#);r#=r#[0]+(r#[1]/1e9)'), + begin: interpolate('s#=n#()'), + end: interpolate('r#=n#(s#);r#=r#[0]+(r#[1]/1e9)'), }); } else if (timer.unit == 'us') { if (timer.ns.stop) { _.assign(templateData, { - 'begin': interpolate('s#=n#.start()'), - 'end': interpolate('r#=n#.microseconds()/1e6'), + begin: interpolate('s#=n#.start()'), + end: interpolate('r#=n#.microseconds()/1e6'), }); } else { _.assign(templateData, { - 'begin': interpolate('s#=n#()'), - 'end': interpolate('r#=(n#()-s#)/1e6'), + begin: interpolate('s#=n#()'), + end: interpolate('r#=(n#()-s#)/1e6'), }); } } else if (timer.ns.now) { _.assign(templateData, { - 'begin': interpolate('s#=n#.now()'), - 'end': interpolate('r#=(n#.now()-s#)/1e3'), + begin: interpolate('s#=n#.now()'), + end: interpolate('r#=(n#.now()-s#)/1e3'), }); } else { _.assign(templateData, { - 'begin': interpolate('s#=new n#().getTime()'), - 'end': interpolate('r#=(new n#().getTime()-s#)/1e3'), + begin: interpolate('s#=new n#().getTime()'), + end: interpolate('r#=(new n#().getTime()-s#)/1e3'), }); } // Define `timer` methods. timer.start = createFunction( interpolate('o#'), - interpolate('var n#=this.ns,${begin};o#.elapsed=0;o#.timeStamp=s#') + interpolate('var n#=this.ns,${begin};o#.elapsed=0;o#.timeStamp=s#'), ); timer.stop = createFunction( interpolate('o#'), - interpolate('var n#=this.ns,s#=o#.timeStamp,${end};o#.elapsed=r#') + interpolate('var n#=this.ns,s#=o#.timeStamp,${end};o#.elapsed=r#'), ); // Create compiled test. return createFunction( interpolate('window,t#'), 'var global = window, clearTimeout = global.clearTimeout, setTimeout = global.setTimeout;\n' + - interpolate(body) + interpolate(body), ); } @@ -1947,7 +1945,7 @@ module.exports = function () { function interpolate(string) { // Replaces all occurrences of `#` with a unique number and template tokens with content. return _.template(string.replace(/\#/g, /\d+/.exec(templateData.uid)))( - templateData + templateData, ); } @@ -1958,7 +1956,7 @@ module.exports = function () { // line switch in at least Chrome 7 to use chrome.Interval try { if ((timer.ns = new (context.chrome || context.chromium).Interval())) { - timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' }); + timers.push({ ns: timer.ns, res: getRes('us'), unit: 'us' }); } } catch (e) {} @@ -1967,7 +1965,7 @@ module.exports = function () { processObject && typeof (timer.ns = processObject.hrtime) == 'function' ) { - timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' }); + timers.push({ ns: timer.ns, res: getRes('ns'), unit: 'ns' }); } // Pick timer with highest resolution. timer = _.minBy(timers, 'res'); @@ -2007,14 +2005,14 @@ module.exports = function () { function enqueue() { queue.push( bench.clone({ - '_original': bench, - 'events': { - 'abort': [update], - 'cycle': [update], - 'error': [update], - 'start': [update], + _original: bench, + events: { + abort: [update], + cycle: [update], + error: [update], + start: [update], }, - }) + }), ); } @@ -2096,12 +2094,12 @@ module.exports = function () { rme = (moe / mean) * 100 || 0; _.assign(bench.stats, { - 'deviation': sd, - 'mean': mean, - 'moe': moe, - 'rme': rme, - 'sem': sem, - 'variance': variance, + deviation: sd, + mean: mean, + moe: moe, + rme: rme, + sem: sem, + variance: variance, }); // Abort the cycle loop when the minimum sample size has been collected @@ -2133,11 +2131,11 @@ module.exports = function () { // Init queue and begin. enqueue(); invoke(queue, { - 'name': 'run', - 'args': { 'async': async }, - 'queued': true, - 'onCycle': evaluate, - 'onComplete': function () { + name: 'run', + args: { async: async }, + queued: true, + onCycle: evaluate, + onComplete: function () { bench.emit('complete'); }, }); @@ -2277,7 +2275,7 @@ module.exports = function () { if (!event.cancelled) { options = { - 'async': + async: ((options = options && options.async) == null ? bench.async : options) && support.timeout, @@ -2315,7 +2313,7 @@ module.exports = function () { * @memberOf Benchmark * @type Object */ - 'options': { + options: { /** * A flag to indicate that benchmark cycles will execute asynchronously * by default. @@ -2323,7 +2321,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type boolean */ - 'async': false, + async: false, /** * A flag to indicate that the benchmark clock is deferred. @@ -2331,14 +2329,14 @@ module.exports = function () { * @memberOf Benchmark.options * @type boolean */ - 'defer': false, + defer: false, /** * The delay between test cycles (secs). * @memberOf Benchmark.options * @type number */ - 'delay': 0.005, + delay: 0.005, /** * Displayed by `Benchmark#toString` when a `name` is not available @@ -2347,7 +2345,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type string */ - 'id': undefined, + id: undefined, /** * The default number of times to execute a test on a benchmark's first cycle. @@ -2355,7 +2353,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type number */ - 'initCount': 1, + initCount: 1, /** * The maximum time a benchmark is allowed to run before finishing (secs). @@ -2365,7 +2363,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type number */ - 'maxTime': 5, + maxTime: 5, /** * The minimum sample size required to perform statistical analysis. @@ -2373,7 +2371,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type number */ - 'minSamples': 5, + minSamples: 5, /** * The time needed to reduce the percent uncertainty of measurement to 1% (secs). @@ -2381,7 +2379,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type number */ - 'minTime': 0, + minTime: 0, /** * The name of the benchmark. @@ -2389,7 +2387,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type string */ - 'name': undefined, + name: undefined, /** * An event listener called when the benchmark is aborted. @@ -2397,7 +2395,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type Function */ - 'onAbort': undefined, + onAbort: undefined, /** * An event listener called when the benchmark completes running. @@ -2405,7 +2403,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type Function */ - 'onComplete': undefined, + onComplete: undefined, /** * An event listener called after each run cycle. @@ -2413,7 +2411,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type Function */ - 'onCycle': undefined, + onCycle: undefined, /** * An event listener called when a test errors. @@ -2421,7 +2419,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type Function */ - 'onError': undefined, + onError: undefined, /** * An event listener called when the benchmark is reset. @@ -2429,7 +2427,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type Function */ - 'onReset': undefined, + onReset: undefined, /** * An event listener called when the benchmark starts running. @@ -2437,7 +2435,7 @@ module.exports = function () { * @memberOf Benchmark.options * @type Function */ - 'onStart': undefined, + onStart: undefined, }, /** @@ -2448,18 +2446,18 @@ module.exports = function () { * @memberOf Benchmark * @type Object */ - 'platform': context.platform || + platform: context.platform || require('platform') || { - 'description': + description: (context.navigator && context.navigator.userAgent) || null, - 'layout': null, - 'product': null, - 'name': null, - 'manufacturer': null, - 'os': null, - 'prerelease': null, - 'version': null, - 'toString': function () { + layout: null, + product: null, + name: null, + manufacturer: null, + os: null, + prerelease: null, + version: null, + toString: function () { return this.description || ''; }, }, @@ -2471,16 +2469,16 @@ module.exports = function () { * @memberOf Benchmark * @type string */ - 'version': '2.1.2', + version: '2.1.2', }); _.assign(Benchmark, { - 'filter': filter, - 'formatNumber': formatNumber, - 'invoke': invoke, - 'join': join, - 'runInContext': runInContext, - 'support': support, + filter: filter, + formatNumber: formatNumber, + invoke: invoke, + join: join, + runInContext: runInContext, + support: support, }); // Add lodash methods to Benchmark. @@ -2488,7 +2486,7 @@ module.exports = function () { ['each', 'forEach', 'forOwn', 'has', 'indexOf', 'map', 'reduce'], function (methodName) { Benchmark[methodName] = _[methodName]; - } + }, ); /*------------------------------------------------------------------------*/ @@ -2500,7 +2498,7 @@ module.exports = function () { * @memberOf Benchmark * @type number */ - 'count': 0, + count: 0, /** * The number of cycles performed while benchmarking. @@ -2508,7 +2506,7 @@ module.exports = function () { * @memberOf Benchmark * @type number */ - 'cycles': 0, + cycles: 0, /** * The number of executions per second. @@ -2516,7 +2514,7 @@ module.exports = function () { * @memberOf Benchmark * @type number */ - 'hz': 0, + hz: 0, /** * The compiled test function. @@ -2524,7 +2522,7 @@ module.exports = function () { * @memberOf Benchmark * @type {Function|string} */ - 'compiled': undefined, + compiled: undefined, /** * The error object if the test failed. @@ -2532,7 +2530,7 @@ module.exports = function () { * @memberOf Benchmark * @type Object */ - 'error': undefined, + error: undefined, /** * The test to benchmark. @@ -2540,7 +2538,7 @@ module.exports = function () { * @memberOf Benchmark * @type {Function|string} */ - 'fn': undefined, + fn: undefined, /** * A flag to indicate if the benchmark is aborted. @@ -2548,7 +2546,7 @@ module.exports = function () { * @memberOf Benchmark * @type boolean */ - 'aborted': false, + aborted: false, /** * A flag to indicate if the benchmark is running. @@ -2556,7 +2554,7 @@ module.exports = function () { * @memberOf Benchmark * @type boolean */ - 'running': false, + running: false, /** * Compiled into the test and executed immediately **before** the test loop. @@ -2619,7 +2617,7 @@ module.exports = function () { * }()) * }()) */ - 'setup': _.noop, + setup: _.noop, /** * Compiled into the test and executed immediately **after** the test loop. @@ -2627,7 +2625,7 @@ module.exports = function () { * @memberOf Benchmark * @type {Function|string} */ - 'teardown': _.noop, + teardown: _.noop, /** * An object of stats including mean, margin or error, and standard deviation. @@ -2635,14 +2633,14 @@ module.exports = function () { * @memberOf Benchmark * @type Object */ - 'stats': { + stats: { /** * The margin of error. * * @memberOf Benchmark#stats * @type number */ - 'moe': 0, + moe: 0, /** * The relative margin of error (expressed as a percentage of the mean). @@ -2650,7 +2648,7 @@ module.exports = function () { * @memberOf Benchmark#stats * @type number */ - 'rme': 0, + rme: 0, /** * The standard error of the mean. @@ -2658,7 +2656,7 @@ module.exports = function () { * @memberOf Benchmark#stats * @type number */ - 'sem': 0, + sem: 0, /** * The sample standard deviation. @@ -2666,7 +2664,7 @@ module.exports = function () { * @memberOf Benchmark#stats * @type number */ - 'deviation': 0, + deviation: 0, /** * The sample arithmetic mean (secs). @@ -2674,7 +2672,7 @@ module.exports = function () { * @memberOf Benchmark#stats * @type number */ - 'mean': 0, + mean: 0, /** * The array of sampled periods. @@ -2682,7 +2680,7 @@ module.exports = function () { * @memberOf Benchmark#stats * @type Array */ - 'sample': [], + sample: [], /** * The sample variance. @@ -2690,7 +2688,7 @@ module.exports = function () { * @memberOf Benchmark#stats * @type number */ - 'variance': 0, + variance: 0, }, /** @@ -2699,14 +2697,14 @@ module.exports = function () { * @memberOf Benchmark * @type Object */ - 'times': { + times: { /** * The time taken to complete the last cycle (secs). * * @memberOf Benchmark#times * @type number */ - 'cycle': 0, + cycle: 0, /** * The time taken to complete the benchmark (secs). @@ -2714,7 +2712,7 @@ module.exports = function () { * @memberOf Benchmark#times * @type number */ - 'elapsed': 0, + elapsed: 0, /** * The time taken to execute the test once (secs). @@ -2722,7 +2720,7 @@ module.exports = function () { * @memberOf Benchmark#times * @type number */ - 'period': 0, + period: 0, /** * A timestamp of when the benchmark started (ms). @@ -2730,21 +2728,21 @@ module.exports = function () { * @memberOf Benchmark#times * @type number */ - 'timeStamp': 0, + timeStamp: 0, }, }); _.assign(Benchmark.prototype, { - 'abort': abort, - 'clone': clone, - 'compare': compare, - 'emit': emit, - 'listeners': listeners, - 'off': off, - 'on': on, - 'reset': reset, - 'run': run, - 'toString': toStringBench, + abort: abort, + clone: clone, + compare: compare, + emit: emit, + listeners: listeners, + off: off, + on: on, + reset: reset, + run: run, + toString: toStringBench, }); /*------------------------------------------------------------------------*/ @@ -2756,7 +2754,7 @@ module.exports = function () { * @memberOf Benchmark.Deferred * @type Object */ - 'benchmark': null, + benchmark: null, /** * The number of deferred cycles performed while benchmarking. @@ -2764,7 +2762,7 @@ module.exports = function () { * @memberOf Benchmark.Deferred * @type number */ - 'cycles': 0, + cycles: 0, /** * The time taken to complete the deferred benchmark (secs). @@ -2772,7 +2770,7 @@ module.exports = function () { * @memberOf Benchmark.Deferred * @type number */ - 'elapsed': 0, + elapsed: 0, /** * A timestamp of when the deferred benchmark started (ms). @@ -2780,11 +2778,11 @@ module.exports = function () { * @memberOf Benchmark.Deferred * @type number */ - 'timeStamp': 0, + timeStamp: 0, }); _.assign(Deferred.prototype, { - 'resolve': resolve, + resolve: resolve, }); /*------------------------------------------------------------------------*/ @@ -2796,7 +2794,7 @@ module.exports = function () { * @memberOf Benchmark.Event * @type boolean */ - 'aborted': false, + aborted: false, /** * A flag to indicate if the default action is cancelled. @@ -2804,7 +2802,7 @@ module.exports = function () { * @memberOf Benchmark.Event * @type boolean */ - 'cancelled': false, + cancelled: false, /** * The object whose listeners are currently being processed. @@ -2812,7 +2810,7 @@ module.exports = function () { * @memberOf Benchmark.Event * @type Object */ - 'currentTarget': undefined, + currentTarget: undefined, /** * The return value of the last executed listener. @@ -2820,7 +2818,7 @@ module.exports = function () { * @memberOf Benchmark.Event * @type Mixed */ - 'result': undefined, + result: undefined, /** * The object to which the event was originally emitted. @@ -2828,7 +2826,7 @@ module.exports = function () { * @memberOf Benchmark.Event * @type Object */ - 'target': undefined, + target: undefined, /** * A timestamp of when the event was created (ms). @@ -2836,7 +2834,7 @@ module.exports = function () { * @memberOf Benchmark.Event * @type number */ - 'timeStamp': 0, + timeStamp: 0, /** * The event type. @@ -2844,7 +2842,7 @@ module.exports = function () { * @memberOf Benchmark.Event * @type string */ - 'type': '', + type: '', }); /*------------------------------------------------------------------------*/ @@ -2863,7 +2861,7 @@ module.exports = function () { * @memberOf Benchmark.Suite.options * @type string */ - 'name': undefined, + name: undefined, }; /*------------------------------------------------------------------------*/ @@ -2875,7 +2873,7 @@ module.exports = function () { * @memberOf Benchmark.Suite * @type number */ - 'length': 0, + length: 0, /** * A flag to indicate if the suite is aborted. @@ -2883,7 +2881,7 @@ module.exports = function () { * @memberOf Benchmark.Suite * @type boolean */ - 'aborted': false, + aborted: false, /** * A flag to indicate if the suite is running. @@ -2891,38 +2889,38 @@ module.exports = function () { * @memberOf Benchmark.Suite * @type boolean */ - 'running': false, + running: false, }); _.assign(Suite.prototype, { - 'abort': abortSuite, - 'add': add, - 'clone': cloneSuite, - 'emit': emit, - 'filter': filterSuite, - 'join': arrayRef.join, - 'listeners': listeners, - 'off': off, - 'on': on, - 'pop': arrayRef.pop, - 'push': push, - 'reset': resetSuite, - 'run': runSuite, - 'reverse': arrayRef.reverse, - 'shift': shift, - 'slice': slice, - 'sort': arrayRef.sort, - 'splice': arrayRef.splice, - 'unshift': unshift, + abort: abortSuite, + add: add, + clone: cloneSuite, + emit: emit, + filter: filterSuite, + join: arrayRef.join, + listeners: listeners, + off: off, + on: on, + pop: arrayRef.pop, + push: push, + reset: resetSuite, + run: runSuite, + reverse: arrayRef.reverse, + shift: shift, + slice: slice, + sort: arrayRef.sort, + splice: arrayRef.splice, + unshift: unshift, }); /*------------------------------------------------------------------------*/ // Expose Deferred, Event, and Suite. _.assign(Benchmark, { - 'Deferred': Deferred, - 'Event': Event, - 'Suite': Suite, + Deferred: Deferred, + Event: Event, + Suite: Suite, }); /*------------------------------------------------------------------------*/ @@ -2937,7 +2935,7 @@ module.exports = function () { push.apply(args, arguments); return func.apply(_, args); }; - } + }, ); // Avoid array-like object bugs with `Array#shift` and `Array#splice` diff --git a/tgui/packages/tgui-bench/package.json b/tgui/packages/tgui-bench/package.json index 6e4083ba63..47cad082fb 100644 --- a/tgui/packages/tgui-bench/package.json +++ b/tgui/packages/tgui-bench/package.json @@ -1,15 +1,14 @@ { "private": true, "name": "tgui-bench", - "version": "4.3.0", + "version": "5.0.0", "dependencies": { - "@fastify/static": "^5.0.2", + "@fastify/static": "^6.12.0", "common": "workspace:*", - "fastify": "^3.29.4", - "inferno": "^7.4.8", - "inferno-vnode-flags": "^7.4.8", + "fastify": "^3.29.5", "lodash": "^4.17.21", "platform": "^1.3.6", + "react": "^18.2.0", "tgui": "workspace:*" } } diff --git a/tgui/packages/tgui-bench/tests/Button.test.tsx b/tgui/packages/tgui-bench/tests/Button.test.tsx index 6b806d720a..0549e69b62 100644 --- a/tgui/packages/tgui-bench/tests/Button.test.tsx +++ b/tgui/packages/tgui-bench/tests/Button.test.tsx @@ -1,11 +1,8 @@ -import { linkEvent } from 'inferno'; import { Button } from 'tgui/components'; import { createRenderer } from 'tgui/renderer'; const render = createRenderer(); -const handleClick = () => undefined; - export const SingleButton = () => { const node = ; render(node); @@ -16,13 +13,6 @@ export const SingleButtonWithCallback = () => { render(node); }; -export const SingleButtonWithLinkEvent = () => { - const node = ( - - ); - render(node); -}; - export const ListOfButtons = () => { const nodes: JSX.Element[] = []; for (let i = 0; i < 100; i++) { @@ -45,19 +35,6 @@ export const ListOfButtonsWithCallback = () => { render(
{nodes}
); }; -export const ListOfButtonsWithLinkEvent = () => { - const nodes: JSX.Element[] = []; - for (let i = 0; i < 100; i++) { - const node = ( - - ); - nodes.push(node); - } - render(
{nodes}
); -}; - export const ListOfButtonsWithIcons = () => { const nodes: JSX.Element[] = []; for (let i = 0; i < 100; i++) { diff --git a/tgui/packages/tgui-bench/tests/DisposalBin.test.tsx b/tgui/packages/tgui-bench/tests/DisposalBin.test.tsx index 5ac9965c76..63093f42f7 100644 --- a/tgui/packages/tgui-bench/tests/DisposalBin.test.tsx +++ b/tgui/packages/tgui-bench/tests/DisposalBin.test.tsx @@ -1,22 +1,19 @@ -import { StoreProvider, configureStore } from 'tgui/store'; - +import { backendUpdate, setGlobalStore } from 'tgui/backend'; import { DisposalBin } from 'tgui/interfaces/DisposalBin'; -import { backendUpdate } from 'tgui/backend'; import { createRenderer } from 'tgui/renderer'; +import { configureStore } from 'tgui/store'; const store = configureStore({ sideEffects: false }); const renderUi = createRenderer((dataJson: string) => { + setGlobalStore(store); + store.dispatch( backendUpdate({ data: Byond.parseJson(dataJson), - }) - ); - return ( - - - + }), ); + return ; }); export const data = JSON.stringify({ diff --git a/tgui/packages/tgui-bench/tests/Tooltip.test.tsx b/tgui/packages/tgui-bench/tests/Tooltip.test.tsx index ea43a61f0b..9dae16f5c0 100644 --- a/tgui/packages/tgui-bench/tests/Tooltip.test.tsx +++ b/tgui/packages/tgui-bench/tests/Tooltip.test.tsx @@ -12,7 +12,7 @@ export const ListOfTooltips = () => { Tooltip #{i} - + , ); } diff --git a/tgui/packages/tgui-dev-server/dreamseeker.js b/tgui/packages/tgui-dev-server/dreamseeker.js index c81f51e35c..d1ca2a9ac5 100644 --- a/tgui/packages/tgui-dev-server/dreamseeker.js +++ b/tgui/packages/tgui-dev-server/dreamseeker.js @@ -6,8 +6,9 @@ import { exec } from 'child_process'; import { promisify } from 'util'; -import { createLogger } from './logging'; -import { require } from './require'; + +import { createLogger } from './logging.js'; +import { require } from './require.js'; const axios = require('axios'); const logger = createLogger('dreamseeker'); @@ -30,7 +31,7 @@ export class DreamSeeker { + '=' + encodeURIComponent(params[key])) .join('&'); logger.log( - `topic call at ${this.client.defaults.baseURL + '/dummy?' + query}` + `topic call at ${this.client.defaults.baseURL + '/dummy?' + query}`, ); return this.client.get('/dummy?' + query); } diff --git a/tgui/packages/tgui-dev-server/index.js b/tgui/packages/tgui-dev-server/index.js index 460b15d99a..85489ebb04 100644 --- a/tgui/packages/tgui-dev-server/index.js +++ b/tgui/packages/tgui-dev-server/index.js @@ -4,8 +4,8 @@ * @license MIT */ -import { createCompiler } from './webpack'; -import { reloadByondCache } from './reloader'; +import { reloadByondCache } from './reloader.js'; +import { createCompiler } from './webpack.js'; const noHot = process.argv.includes('--no-hot'); const noTmp = process.argv.includes('--no-tmp'); diff --git a/tgui/packages/tgui-dev-server/link/client.cjs b/tgui/packages/tgui-dev-server/link/client.cjs index 1e21d42ce8..b0e6f7bc9d 100644 --- a/tgui/packages/tgui-dev-server/link/client.cjs +++ b/tgui/packages/tgui-dev-server/link/client.cjs @@ -31,11 +31,9 @@ const ensureConnection = () => { }; } } -}; -if (process.env.NODE_ENV !== 'production') { window.onunload = () => socket && socket.close(); -} +}; const subscribe = (fn) => subscribers.push(fn); @@ -136,38 +134,38 @@ const sendLogEntry = (level, ns, ...args) => { const setupHotReloading = () => { if ( - // prettier-ignore - process.env.NODE_ENV !== 'production' - && process.env.WEBPACK_HMR_ENABLED - && window.WebSocket + process.env.NODE_ENV === 'production' || + !process.env.WEBPACK_HMR_ENABLED || + !window.WebSocket ) { - if (module.hot) { - ensureConnection(); - sendLogEntry(0, null, 'setting up hot reloading'); - subscribe((msg) => { - const { type } = msg; - sendLogEntry(0, null, 'received', type); - if (type === 'hotUpdate') { - const status = module.hot.status(); - if (status !== 'idle') { - sendLogEntry(0, null, 'hot reload status:', status); - return; - } - module.hot - .check({ - ignoreUnaccepted: true, - ignoreDeclined: true, - ignoreErrored: true, - }) - .then((modules) => { - sendLogEntry(0, null, 'outdated modules', modules); - }) - .catch((err) => { - sendLogEntry(0, null, 'reload error', err); - }); + return; + } + if (module.hot) { + ensureConnection(); + sendLogEntry(0, null, 'setting up hot reloading'); + subscribe((msg) => { + const { type } = msg; + sendLogEntry(0, null, 'received', type); + if (type === 'hotUpdate') { + const status = module.hot.status(); + if (status !== 'idle') { + sendLogEntry(0, null, 'hot reload status:', status); + return; } - }); - } + module.hot + .check({ + ignoreUnaccepted: true, + ignoreDeclined: true, + ignoreErrored: true, + }) + .then((modules) => { + sendLogEntry(0, null, 'outdated modules', modules); + }) + .catch((err) => { + sendLogEntry(0, null, 'reload error', err); + }); + } + }); } }; diff --git a/tgui/packages/tgui-dev-server/link/retrace.js b/tgui/packages/tgui-dev-server/link/retrace.js index 949835c700..083ddb37d1 100644 --- a/tgui/packages/tgui-dev-server/link/retrace.js +++ b/tgui/packages/tgui-dev-server/link/retrace.js @@ -6,9 +6,10 @@ import fs from 'fs'; import { basename } from 'path'; -import { createLogger } from '../logging'; -import { require } from '../require'; -import { resolveGlob } from '../util'; + +import { createLogger } from '../logging.js'; +import { require } from '../require.js'; +import { resolveGlob } from '../util.js'; const SourceMap = require('source-map'); const { parse: parseStackTrace } = require('stacktrace-parser'); @@ -30,7 +31,7 @@ export const loadSourceMaps = async (bundleDir) => { try { const file = basename(path).replace('.map', ''); const consumer = await new SourceMapConsumer( - JSON.parse(fs.readFileSync(path, 'utf8')) + JSON.parse(fs.readFileSync(path, 'utf8')), ); sourceMaps.push({ file, consumer }); } catch (err) { diff --git a/tgui/packages/tgui-dev-server/link/server.js b/tgui/packages/tgui-dev-server/link/server.js index f0c0d153d3..2a1f551bf6 100644 --- a/tgui/packages/tgui-dev-server/link/server.js +++ b/tgui/packages/tgui-dev-server/link/server.js @@ -6,9 +6,10 @@ import http from 'http'; import { inspect } from 'util'; -import { createLogger, directLog } from '../logging'; -import { require } from '../require'; -import { loadSourceMaps, retrace } from './retrace'; + +import { createLogger, directLog } from '../logging.js'; +import { require } from '../require.js'; +import { loadSourceMaps, retrace } from './retrace.js'; const WebSocket = require('ws'); diff --git a/tgui/packages/tgui-dev-server/package.json b/tgui/packages/tgui-dev-server/package.json index ab45774318..496e25c6c1 100644 --- a/tgui/packages/tgui-dev-server/package.json +++ b/tgui/packages/tgui-dev-server/package.json @@ -1,13 +1,13 @@ { "private": true, "name": "tgui-dev-server", - "version": "4.4.1", + "version": "5.0.0", "type": "module", "dependencies": { - "axios": "^1.6.0", - "glob": "^8.0.3", - "source-map": "^0.7.3", + "axios": "^1.6.2", + "glob": "^7.2.0", + "source-map": "^0.7.4", "stacktrace-parser": "^0.1.10", - "ws": "^8.6.0" + "ws": "^8.14.2" } } diff --git a/tgui/packages/tgui-dev-server/reloader.js b/tgui/packages/tgui-dev-server/reloader.js index aed9a7dcd7..cb477a6523 100644 --- a/tgui/packages/tgui-dev-server/reloader.js +++ b/tgui/packages/tgui-dev-server/reloader.js @@ -7,10 +7,11 @@ import fs from 'fs'; import os from 'os'; import { basename } from 'path'; -import { DreamSeeker } from './dreamseeker'; -import { createLogger } from './logging'; -import { resolveGlob, resolvePath } from './util'; -import { regQuery } from './winreg'; + +import { DreamSeeker } from './dreamseeker.js'; +import { createLogger } from './logging.js'; +import { resolveGlob, resolvePath } from './util.js'; +import { regQuery } from './winreg.js'; const logger = createLogger('reloader'); @@ -83,19 +84,19 @@ export const reloadByondCache = async (bundleDir) => { } // Get dreamseeker instances const pids = cacheDirs.map((cacheDir) => - parseInt(cacheDir.split('/cache/tmp').pop(), 10) + parseInt(cacheDir.split('/cache/tmp').pop(), 10), ); const dssPromise = DreamSeeker.getInstancesByPids(pids); // Copy assets const assets = await resolveGlob( bundleDir, - './*.+(bundle|chunk|hot-update).*' + './*.+(bundle|chunk|hot-update).*', ); for (let cacheDir of cacheDirs) { // Clear garbage const garbage = await resolveGlob( cacheDir, - './*.+(bundle|chunk|hot-update).*' + './*.+(bundle|chunk|hot-update).*', ); try { // Plant a dummy browser window file, we'll be using this to avoid world topic. For byond 515. diff --git a/tgui/packages/tgui-dev-server/util.js b/tgui/packages/tgui-dev-server/util.js index d60ebb212f..79190fe189 100644 --- a/tgui/packages/tgui-dev-server/util.js +++ b/tgui/packages/tgui-dev-server/util.js @@ -6,7 +6,8 @@ import fs from 'fs'; import path from 'path'; -import { require } from './require'; + +import { require } from './require.js'; const globPkg = require('glob'); diff --git a/tgui/packages/tgui-dev-server/webpack.js b/tgui/packages/tgui-dev-server/webpack.js index 1c16345a89..e4fbdeb9f1 100644 --- a/tgui/packages/tgui-dev-server/webpack.js +++ b/tgui/packages/tgui-dev-server/webpack.js @@ -7,10 +7,11 @@ import fs from 'fs'; import { createRequire } from 'module'; import { dirname } from 'path'; -import { loadSourceMaps, setupLink } from './link/server'; -import { createLogger } from './logging'; -import { reloadByondCache } from './reloader'; -import { resolveGlob } from './util'; + +import { loadSourceMaps, setupLink } from './link/server.js'; +import { createLogger } from './logging.js'; +import { reloadByondCache } from './reloader.js'; +import { resolveGlob } from './util.js'; const logger = createLogger('webpack'); diff --git a/tgui/packages/tgui-dev-server/winreg.js b/tgui/packages/tgui-dev-server/winreg.js index d7408b5c39..43a4170190 100644 --- a/tgui/packages/tgui-dev-server/winreg.js +++ b/tgui/packages/tgui-dev-server/winreg.js @@ -8,7 +8,8 @@ import { exec } from 'child_process'; import { promisify } from 'util'; -import { createLogger } from './logging'; + +import { createLogger } from './logging.js'; const logger = createLogger('winreg'); @@ -35,8 +36,8 @@ export const regQuery = async (path, key) => { logger.error('could not find the start of the key value'); return null; } - const value = stdout.substring(indexOfValue + 4, indexOfEol); - return value; + + return stdout.substring(indexOfValue + 4, indexOfEol); } catch (err) { logger.error(err); return null; diff --git a/tgui/packages/tgui-panel/Panel.tsx b/tgui/packages/tgui-panel/Panel.tsx index 8171333866..10088e0954 100644 --- a/tgui/packages/tgui-panel/Panel.tsx +++ b/tgui/packages/tgui-panel/Panel.tsx @@ -6,6 +6,7 @@ import { Button, Section, Stack } from 'tgui/components'; import { Pane } from 'tgui/layouts'; + import { NowPlayingWidget, useAudio } from './audio'; import { ChatPanel, ChatTabs } from './chat'; import { useGame } from './game'; @@ -14,13 +15,13 @@ import { PingIndicator } from './ping'; import { ReconnectButton } from './reconnect'; import { SettingsPanel, useSettings } from './settings'; -export const Panel = (props, context) => { - const audio = useAudio(context); - const settings = useSettings(context); - const game = useGame(context); +export const Panel = (props) => { + const audio = useAudio(); + const settings = useSettings(); + const game = useGame(); if (process.env.NODE_ENV !== 'production') { const { useDebug, KitchenSink } = require('tgui/debug'); - const debug = useDebug(context); + const debug = useDebug(); if (debug.kitchenSink) { return ; } diff --git a/tgui/packages/tgui-panel/audio/NowPlayingWidget.jsx b/tgui/packages/tgui-panel/audio/NowPlayingWidget.jsx index 30052c3f3a..903dacf284 100644 --- a/tgui/packages/tgui-panel/audio/NowPlayingWidget.jsx +++ b/tgui/packages/tgui-panel/audio/NowPlayingWidget.jsx @@ -5,15 +5,15 @@ */ import { toFixed } from 'common/math'; -import { useDispatch, useSelector } from 'common/redux'; +import { useDispatch, useSelector } from 'tgui/backend'; import { Button, Collapsible, Flex, Knob, Section } from 'tgui/components'; import { useSettings } from '../settings'; import { selectAudio } from './selectors'; -export const NowPlayingWidget = (props, context) => { - const audio = useSelector(context, selectAudio), - dispatch = useDispatch(context), - settings = useSettings(context), +export const NowPlayingWidget = (props) => { + const audio = useSelector(selectAudio), + dispatch = useDispatch(), + settings = useSettings(), title = audio.meta?.title, URL = audio.meta?.link, Artist = audio.meta?.artist || 'Unknown Artist', @@ -22,10 +22,10 @@ export const NowPlayingWidget = (props, context) => { duration = audio.meta?.duration, date = !isNaN(upload_date) ? upload_date?.substring(0, 4) + - '-' + - upload_date?.substring(4, 6) + - '-' + - upload_date?.substring(6, 8) + '-' + + upload_date?.substring(4, 6) + + '-' + + upload_date?.substring(6, 8) : upload_date; return ( @@ -35,10 +35,11 @@ export const NowPlayingWidget = (props, context) => { mx={0.5} grow={1} style={{ - 'white-space': 'nowrap', - 'overflow': 'hidden', - 'text-overflow': 'ellipsis', - }}> + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }} + > {
diff --git a/tgui/packages/tgui-panel/audio/hooks.ts b/tgui/packages/tgui-panel/audio/hooks.ts index 504b4f5c6e..2e9d830cb7 100644 --- a/tgui/packages/tgui-panel/audio/hooks.ts +++ b/tgui/packages/tgui-panel/audio/hooks.ts @@ -4,12 +4,13 @@ * @license MIT */ -import { useSelector, useDispatch } from 'common/redux'; +import { useDispatch, useSelector } from 'tgui/backend'; + import { selectAudio } from './selectors'; -export const useAudio = (context) => { - const state = useSelector(context, selectAudio); - const dispatch = useDispatch(context); +export const useAudio = () => { + const state = useSelector(selectAudio); + const dispatch = useDispatch(); return { ...state, toggle: () => dispatch({ type: 'audio/toggle' }), diff --git a/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx b/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx index 2ccc491d95..07cde398ea 100644 --- a/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx +++ b/tgui/packages/tgui-panel/chat/ChatPageSettings.jsx @@ -4,15 +4,29 @@ * @license MIT */ -import { useDispatch, useSelector } from 'common/redux'; -import { Button, Collapsible, Divider, Input, Section, Stack } from 'tgui/components'; -import { moveChatPageLeft, moveChatPageRight, removeChatPage, toggleAcceptedType, updateChatPage } from './actions'; +import { useDispatch, useSelector } from 'tgui/backend'; +import { + Button, + Collapsible, + Divider, + Input, + Section, + Stack, +} from 'tgui/components'; + +import { + moveChatPageLeft, + moveChatPageRight, + removeChatPage, + toggleAcceptedType, + updateChatPage, +} from './actions'; import { MESSAGE_TYPES } from './constants'; import { selectCurrentChatPage } from './selectors'; -export const ChatPageSettings = (props, context) => { - const page = useSelector(context, selectCurrentChatPage); - const dispatch = useDispatch(context); +export const ChatPageSettings = (props) => { + const page = useSelector(selectCurrentChatPage); + const dispatch = useDispatch(); return (
@@ -25,7 +39,7 @@ export const ChatPageSettings = (props, context) => { updateChatPage({ pageId: page.id, name: value, - }) + }), ) } /> @@ -39,9 +53,10 @@ export const ChatPageSettings = (props, context) => { dispatch( removeChatPage({ pageId: page.id, - }) + }), ) - }> + } + > Remove @@ -60,9 +75,10 @@ export const ChatPageSettings = (props, context) => { dispatch( moveChatPageLeft({ pageId: page.id, - }) + }), ) - }> + } + > « @@ -84,7 +101,7 @@ export const ChatPageSettings = (props, context) => {
{MESSAGE_TYPES.filter( - (typeDef) => !typeDef.important && !typeDef.admin + (typeDef) => !typeDef.important && !typeDef.admin, ).map((typeDef) => ( { toggleAcceptedType({ pageId: page.id, type: typeDef.type, - }) + }), ) - }> + } + > {typeDef.name} ))} {MESSAGE_TYPES.filter( - (typeDef) => !typeDef.important && typeDef.admin + (typeDef) => !typeDef.important && typeDef.admin, ).map((typeDef) => ( { toggleAcceptedType({ pageId: page.id, type: typeDef.type, - }) + }), ) - }> + } + > {typeDef.name} ))} diff --git a/tgui/packages/tgui-panel/chat/ChatPanel.jsx b/tgui/packages/tgui-panel/chat/ChatPanel.jsx index 3132a66ce7..845c161275 100644 --- a/tgui/packages/tgui-panel/chat/ChatPanel.jsx +++ b/tgui/packages/tgui-panel/chat/ChatPanel.jsx @@ -5,13 +5,14 @@ */ 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 +27,7 @@ export class ChatPanel extends Component { chatRenderer.mount(this.ref.current); chatRenderer.events.on( 'scrollTrackingChanged', - this.handleScrollTrackingChange + this.handleScrollTrackingChange, ); this.componentDidUpdate(); } @@ -34,7 +35,7 @@ export class ChatPanel extends Component { componentWillUnmount() { chatRenderer.events.off( 'scrollTrackingChanged', - this.handleScrollTrackingChange + this.handleScrollTrackingChange, ); } @@ -46,7 +47,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 +64,8 @@ export class ChatPanel extends Component { )} diff --git a/tgui/packages/tgui-panel/chat/ChatTabs.jsx b/tgui/packages/tgui-panel/chat/ChatTabs.jsx index 1d4f6f65ed..2031f6a255 100644 --- a/tgui/packages/tgui-panel/chat/ChatTabs.jsx +++ b/tgui/packages/tgui-panel/chat/ChatTabs.jsx @@ -4,30 +4,32 @@ * @license MIT */ -import { useDispatch, useSelector } from 'common/redux'; -import { Box, Tabs, Flex, Button } from 'tgui/components'; -import { changeChatPage, addChatPage } from './actions'; -import { selectChatPages, selectCurrentChatPage } from './selectors'; +import { useDispatch, useSelector } from 'tgui/backend'; +import { Box, Button, Flex, Tabs } from 'tgui/components'; + import { openChatSettings } from '../settings/actions'; +import { addChatPage, changeChatPage } from './actions'; +import { selectChatPages, selectCurrentChatPage } from './selectors'; const UnreadCountWidget = ({ value }) => ( + fontSize: '0.7em', + borderRadius: '0.25em', + width: '1.7em', + lineHeight: '1.55em', + backgroundColor: 'crimson', + color: '#fff', + }} + > {Math.min(value, 99)} ); -export const ChatTabs = (props, context) => { - const pages = useSelector(context, selectChatPages); - const currentPage = useSelector(context, selectCurrentChatPage); - const dispatch = useDispatch(context); +export const ChatTabs = (props) => { + const pages = useSelector(selectChatPages); + const currentPage = useSelector(selectCurrentChatPage); + const dispatch = useDispatch(); return ( @@ -45,9 +47,10 @@ export const ChatTabs = (props, context) => { dispatch( changeChatPage({ pageId: page.id, - }) + }), ) - }> + } + > {page.name} ))} diff --git a/tgui/packages/tgui-panel/chat/actions.js b/tgui/packages/tgui-panel/chat/actions.js index 37fb48cc81..0f87a8b0e2 100644 --- a/tgui/packages/tgui-panel/chat/actions.js +++ b/tgui/packages/tgui-panel/chat/actions.js @@ -5,6 +5,7 @@ */ import { createAction } from 'common/redux'; + import { createPage } from './model'; export const loadChat = createAction('chat/load'); diff --git a/tgui/packages/tgui-panel/chat/constants.ts b/tgui/packages/tgui-panel/chat/constants.ts index 978e7b72e2..51b583a055 100644 --- a/tgui/packages/tgui-panel/chat/constants.ts +++ b/tgui/packages/tgui-panel/chat/constants.ts @@ -6,7 +6,7 @@ // export const MAX_VISIBLE_MESSAGES = 2500; No longer a constant // export const MAX_PERSISTED_MESSAGES = 1000; No longer a constant -export const MESSAGE_SAVE_INTERVAL = 10000; +// export const MESSAGE_SAVE_INTERVAL = 10000; No longer a constant export const MESSAGE_PRUNE_INTERVAL = 60000; // export const COMBINE_MAX_MESSAGES = 5; No longer a constant // export const COMBINE_MAX_TIME_WINDOW = 5000; No longer a constant @@ -26,6 +26,7 @@ export const MESSAGE_TYPE_LOCALCHAT = 'localchat'; export const MESSAGE_TYPE_NPCEMOTE = 'npcemote'; export const MESSAGE_TYPE_PLOCALCHAT = 'plocalchat'; export const MESSAGE_TYPE_VORE = 'vore'; +export const MESSAGE_TYPE_HIVEMIND = 'hivemind'; export const MESSAGE_TYPE_RADIO = 'radio'; export const MESSAGE_TYPE_NIF = 'nif'; export const MESSAGE_TYPE_INFO = 'info'; @@ -61,7 +62,7 @@ export const MESSAGE_TYPES = [ type: MESSAGE_TYPE_NPCEMOTE, // Needs to be first name: 'NPC Emotes / Says', description: 'In-character emotes and says from NPCs', - selector: '.npcemote, .npcsay', + selector: '.npcemote, .npcsay :not(.radio)', }, { type: MESSAGE_TYPE_LOCALCHAT, @@ -81,12 +82,18 @@ export const MESSAGE_TYPES = [ description: 'Messages regarding vore interactions', selector: '.valert, .vwarning, .vnotice, .vdanger', }, + { + type: MESSAGE_TYPE_HIVEMIND, + name: 'Global Say', + description: 'All global languages (Hivemind / Binary)', + selector: '.hivemind, .binarysay', + }, { type: MESSAGE_TYPE_RADIO, name: 'Radio', description: 'All departments of radio messages', selector: - '.alert, .minorannounce, .syndradio, .centradio, .airadio, .comradio, .secradio, .gangradio, .engradio, .medradio, .sciradio, .supradio, .srvradio, .expradio, .radio, .deptradio, .binarysay, .newscaster, .resonate, .abductor, .alien, .changeling', + '.alert, .minorannounce, .syndradio, .centradio, .airadio, .comradio, .secradio, .gangradio, .engradio, .medradio, .sciradio, .supradio, .srvradio, .expradio, .radio, .deptradio, .newscaster, .resonate, .abductor, .alien, .changeling', }, { type: MESSAGE_TYPE_NIF, @@ -99,7 +106,7 @@ export const MESSAGE_TYPES = [ name: 'Info', description: 'Non-urgent messages from the game and items', selector: - '.notice:not(.pm), .adminnotice, .info, .sinister, .cult, .infoplain, .announce, .hear, .smallnotice, .holoparasite, .boldnotice', + '.notice:not(.pm), .adminnotice:not(.pm), .info, .sinister, .cult, .infoplain, .announce, .hear, .smallnotice, .holoparasite, .boldnotice', }, { type: MESSAGE_TYPE_WARNING, diff --git a/tgui/packages/tgui-panel/chat/middleware.js b/tgui/packages/tgui-panel/chat/middleware.js index 7c90df4410..99cbd8b7e1 100644 --- a/tgui/packages/tgui-panel/chat/middleware.js +++ b/tgui/packages/tgui-panel/chat/middleware.js @@ -4,25 +4,47 @@ * @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 { selectGame } from '../game/selectors'; +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 { MESSAGE_SAVE_INTERVAL } from './constants'; +import { + addChatPage, + changeChatPage, + changeScrollTracking, + loadChat, + moveChatPageLeft, + moveChatPageRight, + purgeChatMessageArchive, + rebuildChat, + removeChatPage, + saveChatToDisk, + toggleAcceptedType, + updateMessageCount, +} from './actions'; import { createMessage, serializeMessage } from './model'; import { chatRenderer } from './renderer'; import { selectChat, selectCurrentChatPage } from './selectors'; // List of blacklisted tags const FORBID_TAGS = ['a', 'iframe', 'link', 'video']; +let storedRounds = []; +let storedLines = []; const saveChatToStorage = async (store) => { const state = selectChat(store.getState()); 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 +53,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 }; @@ -65,32 +87,49 @@ const loadChatFromStorage = async (store) => { }); } if (archivedMessages) { - chatRenderer.archivedMessages = archivedMessages; - - /* FIXME Implement this later on + for (let archivedMessage of archivedMessages) { + if (archivedMessage.html) { + archivedMessage.html = DOMPurify.sanitize(archivedMessage.html, { + FORBID_TAGS, + }); + } + } const settings = selectSettings(store.getState()); - const filteredMessages = []; // Checks if the setting is actually set or set to -1 (infinite) // Otherwise make it grow infinitely - if (settings.logRetainDays || settings.logRetainDays === -1) { - const limit = new Date(); - limit.setDate(limit.getMinutes() - settings.logRetainDays); + if (settings.logRetainRounds) { + storedRounds = []; + storedLines = []; + let oldId = null; + let currentLine = 0; + settings.storedRounds = 0; + settings.exportStart = 0; + settings.exportEnd = 0; for (let message of archivedMessages) { - const timestamp = new Date(message.createdAt); - timestamp.setDate(timestamp.getMinutes() - settings.logRetainDays); - - if (timestamp.getDate() < limit.getDate()) { - filteredMessages.push(message); + const currentId = message.roundId; + if (currentId !== oldId) { + const round = currentId; + const line = currentLine; + storedRounds.push(round); + storedLines.push(line); + oldId = currentId; + currentLine++; } } - - archivedMessages.length = 0; + if (storedRounds.length > settings.logRetainRounds) { + chatRenderer.archivedMessages = archivedMessages.slice( + storedLines[storedRounds.length - settings.logRetainRounds], + ); + settings.storedRounds = settings.logRetainRounds; + } else { + chatRenderer.archivedMessages = archivedMessages; + } + settings.lastId = oldId; + } else { + chatRenderer.archivedMessages = archivedMessages; } - - chatRenderer.archivedMessages = filteredMessages; - */ } store.dispatch(loadChat(state)); }; @@ -111,21 +150,28 @@ export const chatMiddleware = (store) => { chatRenderer.events.on('scrollTrackingChanged', (scrollTracking) => { store.dispatch(changeScrollTracking(scrollTracking)); }); - setInterval(() => { - saveChatToStorage(store); - }, MESSAGE_SAVE_INTERVAL); return (next) => (action) => { const { type, payload } = action; const settings = selectSettings(store.getState()); + const game = selectGame(store.getState()); settings.totalStoredMessages = chatRenderer.getStoredMessages(); + settings.storedRounds = storedRounds.length; chatRenderer.setVisualChatLimits( settings.visibleMessageLimit, settings.combineMessageLimit, settings.combineIntervalLimit, - settings.logLineCount + settings.logLineCount, + settings.logEnable, + settings.logLimit, + settings.storedTypes, + game.roundId, ); - if (!initialized) { + // Load the chat once settings are loaded + if (!initialized && settings.initialized) { initialized = true; + setInterval(() => { + saveChatToStorage(store); + }, settings.saveInterval * 1000); loadChatFromStorage(store); } if (type === 'chat/message') { @@ -166,6 +212,11 @@ export const chatMiddleware = (store) => { chatRenderer.processBatch([payload_obj.content], { doArchive: true, }); + if (game.roundId !== settings.lastId) { + storedRounds.push(game.roundId); + storedLines.push(settings.totalStoredMessages - 1); + settings.lastId = game.roundId; + } return; } if (type === loadChat.type) { @@ -204,7 +255,7 @@ export const chatMiddleware = (store) => { next(action); chatRenderer.setHighlight( settings.highlightSettings, - settings.highlightSettingById + settings.highlightSettingById, ); return; @@ -215,11 +266,21 @@ export const chatMiddleware = (store) => { return next(action); } if (type === saveChatToDisk.type) { - chatRenderer.saveToDisk(settings.logLineCount); + chatRenderer.saveToDisk( + settings.logLineCount, + storedLines[storedLines.length - settings.exportEnd], + storedLines[storedLines.length - settings.exportStart], + ); return; } if (type === purgeChatMessageArchive.type) { chatRenderer.purgeMessageArchive(); + storedRounds = []; + storedLines = []; + settings.lastId = null; + settings.storedRounds = 0; + settings.exportStart = 0; + settings.exportEnd = 0; return; } return next(action); diff --git a/tgui/packages/tgui-panel/chat/model.js b/tgui/packages/tgui-panel/chat/model.js index e544ebfddd..b2f3953400 100644 --- a/tgui/packages/tgui-panel/chat/model.js +++ b/tgui/packages/tgui-panel/chat/model.js @@ -5,11 +5,14 @@ */ 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]; +export const canStoreType = (storedTypes, type) => storedTypes[type]; + export const createPage = (obj) => { let acceptedTypes = {}; @@ -42,6 +45,7 @@ export const createMainPage = () => { export const createMessage = (payload) => ({ createdAt: Date.now(), + roundId: null, ...payload, }); @@ -51,6 +55,7 @@ export const serializeMessage = (message, archive = false) => ({ html: archive ? message.node.outerHTML : message.html, times: message.times, createdAt: message.createdAt, + roundId: message.roundId, }); export const isSameMessage = (a, b) => diff --git a/tgui/packages/tgui-panel/chat/reducer.js b/tgui/packages/tgui-panel/chat/reducer.js index 488bd99320..effc942395 100644 --- a/tgui/packages/tgui-panel/chat/reducer.js +++ b/tgui/packages/tgui-panel/chat/reducer.js @@ -4,7 +4,18 @@ * @license MIT */ -import { addChatPage, changeChatPage, loadChat, removeChatPage, moveChatPageLeft, moveChatPageRight, toggleAcceptedType, updateChatPage, updateMessageCount, changeScrollTracking } from './actions'; +import { + addChatPage, + changeChatPage, + changeScrollTracking, + loadChat, + moveChatPageLeft, + moveChatPageRight, + removeChatPage, + toggleAcceptedType, + updateChatPage, + updateMessageCount, +} from './actions'; import { canPageAcceptType, createMainPage } from './model'; const mainPage = createMainPage(); diff --git a/tgui/packages/tgui-panel/chat/renderer.jsx b/tgui/packages/tgui-panel/chat/renderer.jsx index 3b060548bb..fc236e43c7 100644 --- a/tgui/packages/tgui-panel/chat/renderer.jsx +++ b/tgui/packages/tgui-panel/chat/renderer.jsx @@ -6,12 +6,27 @@ import { EventEmitter } from 'common/events'; import { classes } from 'common/react'; +import { render } from 'react-dom'; 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 { highlightNode, linkifyNode } from './replaceInTextNode'; + import { Tooltip } from '../../tgui/components'; +import { + IMAGE_RETRY_DELAY, + IMAGE_RETRY_LIMIT, + IMAGE_RETRY_MESSAGE_AGE, + MESSAGE_PRUNE_INTERVAL, + MESSAGE_TYPE_INTERNAL, + MESSAGE_TYPE_UNKNOWN, + MESSAGE_TYPES, +} from './constants'; +import { + canPageAcceptType, + canStoreType, + createMessage, + isSameMessage, + serializeMessage, +} from './model'; +import { highlightNode, linkifyNode } from './replaceInTextNode'; const logger = createLogger('chatRenderer'); @@ -27,8 +42,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) => { @@ -119,7 +134,11 @@ class ChatRenderer { this.visibleMessageLimit = 2500; this.combineMessageLimit = 5; this.combineIntervalLimit = 5; - this.exportLimit = -1; + this.exportLimit = 0; + this.logLimit = 0; + this.logEnable = true; + this.roundId = null; + this.storedTypes = {}; // Scroll handler /** @type {HTMLElement} */ this.scrollNode = null; @@ -213,7 +232,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 +251,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 +321,7 @@ class ChatRenderer { highlightRegex = new RegExp('(' + regexStr + ')', flags); } else { const pattern = `${matchWord ? '\\b' : ''}(${highlightWords.join( - '|' + '|', )})${matchWord ? '\\b' : ''}`; highlightRegex = new RegExp(pattern, flags); } @@ -361,12 +380,20 @@ class ChatRenderer { visibleMessageLimit, combineMessageLimit, combineIntervalLimit, - exportLimit + exportLimit, + logEnable, + logLimit, + storedTypes, + roundId, ) { this.visibleMessageLimit = visibleMessageLimit; this.combineMessageLimit = combineMessageLimit; this.combineIntervalLimit = combineIntervalLimit; this.exportLimit = exportLimit; + this.logEnable = logEnable; + this.logLimit = logLimit; + this.storedTypes = storedTypes; + this.roundId = roundId; } getCombinableMessage(predicate) { @@ -485,7 +512,7 @@ class ChatRenderer { , - childNode + childNode, ); /* eslint-enable react/no-danger */ } @@ -504,7 +531,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 +564,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; } @@ -548,7 +575,26 @@ class ChatRenderer { countByType[message.type] += 1; // TODO: Detect duplicates this.messages.push(message); - if (doArchive) { + if ( + doArchive && + this.logEnable && + this.storedTypes && + canStoreType(this.storedTypes, message.type) + ) { + message.roundId = this.roundId; + if ( + this.logLimit > 0 && + this.archivedMessages.length >= this.logLimit + 1 + ) { + this.archivedMessages = this.archivedMessages.slice( + -(this.logLimit - 1), + ); + } else if ( + this.logLimit > 0 && + this.archivedMessages.length >= this.logLimit + ) { + this.archivedMessages.shift(); + } this.archivedMessages.push(serializeMessage(message, true)); // TODO: Actually having a better message archiving maybe for exports? } if (canPageAcceptType(this.page, message.type)) { @@ -598,7 +644,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 +653,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); @@ -637,7 +683,7 @@ class ChatRenderer { }); } - saveToDisk(logLineCount) { + saveToDisk(logLineCount, startLine = 0, endLine = 0) { // Allow only on IE11 if (Byond.IS_LTE_IE10) { return; @@ -659,7 +705,16 @@ class ChatRenderer { let messagesHtml = ''; let tmpMsgArray = []; - if (logLineCount > 0) { + if (startLine || endLine) { + if (!endLine) { + tmpMsgArray = this.archivedMessages.slice(startLine); + } else { + tmpMsgArray = this.archivedMessages.slice(startLine, endLine); + } + if (logLineCount > 0) { + tmpMsgArray = tmpMsgArray.slice(-logLineCount); + } + } else if (logLineCount > 0) { tmpMsgArray = this.archivedMessages.slice(-logLineCount); } else { tmpMsgArray = this.archivedMessages; diff --git a/tgui/packages/tgui-panel/chat/replaceInTextNode.js b/tgui/packages/tgui-panel/chat/replaceInTextNode.js index 5c345e90a1..4c91b604da 100644 --- a/tgui/packages/tgui-panel/chat/replaceInTextNode.js +++ b/tgui/packages/tgui-panel/chat/replaceInTextNode.js @@ -149,7 +149,7 @@ export const highlightNode = ( node, regex, words, - createNode = createHighlightNode + createNode = createHighlightNode, ) => { if (!createNode) { createNode = createHighlightNode; diff --git a/tgui/packages/tgui-panel/game/hooks.ts b/tgui/packages/tgui-panel/game/hooks.ts index 859aaa09a4..40a74ff44a 100644 --- a/tgui/packages/tgui-panel/game/hooks.ts +++ b/tgui/packages/tgui-panel/game/hooks.ts @@ -4,9 +4,10 @@ * @license MIT */ -import { useSelector } from 'common/redux'; +import { useSelector } from 'tgui/backend'; + import { selectGame } from './selectors'; -export const useGame = (context) => { - return useSelector(context, selectGame); +export const useGame = () => { + return useSelector(selectGame); }; diff --git a/tgui/packages/tgui-panel/game/middleware.js b/tgui/packages/tgui-panel/game/middleware.js index 53dd45bb46..1cf80a7ac6 100644 --- a/tgui/packages/tgui-panel/game/middleware.js +++ b/tgui/packages/tgui-panel/game/middleware.js @@ -6,8 +6,8 @@ import { pingSoft, pingSuccess } from '../ping/actions'; import { connectionLost, connectionRestored, roundRestarted } from './actions'; -import { selectGame } from './selectors'; import { CONNECTION_LOST_AFTER } from './constants'; +import { selectGame } from './selectors'; const withTimestamp = (action) => ({ ...action, diff --git a/tgui/packages/tgui-panel/index.tsx b/tgui/packages/tgui-panel/index.tsx index 2c5ef175a8..92b7f2e4e3 100644 --- a/tgui/packages/tgui-panel/index.tsx +++ b/tgui/packages/tgui-panel/index.tsx @@ -7,15 +7,18 @@ // Themes import './styles/main.scss'; import './styles/themes/light.scss'; +import './styles/themes/vchatlight.scss'; 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 +26,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 +54,7 @@ const renderApp = createRenderer(() => { setGlobalStore(store); const { Panel } = require('./Panel'); - return ( - - - - ); + return ; }); const setupApp = () => { @@ -85,14 +83,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 +111,7 @@ const setupApp = () => { ], () => { renderApp(); - } + }, ); } }; diff --git a/tgui/packages/tgui-panel/package.json b/tgui/packages/tgui-panel/package.json index 7aab57e169..f020764a31 100644 --- a/tgui/packages/tgui-panel/package.json +++ b/tgui/packages/tgui-panel/package.json @@ -6,8 +6,9 @@ "@types/node": "^14.x", "@types/react": "^18.2.42", "common": "workspace:*", - "dompurify": "^2.3.1", - "inferno": "^7.4.8", + "dompurify": "^2.4.4", + "react": "^18.2.0", + "react-dom": "^18.2.0", "tgui": "workspace:*", "tgui-dev-server": "workspace:*", "tgui-polyfill": "workspace:*" diff --git a/tgui/packages/tgui-panel/ping/PingIndicator.jsx b/tgui/packages/tgui-panel/ping/PingIndicator.jsx index aadbd1c134..b2355820e5 100644 --- a/tgui/packages/tgui-panel/ping/PingIndicator.jsx +++ b/tgui/packages/tgui-panel/ping/PingIndicator.jsx @@ -6,12 +6,12 @@ import { Color } from 'common/color'; import { toFixed } from 'common/math'; -import { useSelector } from 'common/redux'; +import { useSelector } from 'tgui/backend'; import { Box } from 'tgui/components'; import { selectPing } from './selectors'; -export const PingIndicator = (props, context) => { - const ping = useSelector(context, selectPing); +export const PingIndicator = (props) => { + const ping = useSelector(selectPing); const color = Color.lookup(ping.networkQuality, [ new Color(220, 40, 40), new Color(220, 200, 40), diff --git a/tgui/packages/tgui-panel/ping/reducer.ts b/tgui/packages/tgui-panel/ping/reducer.ts index fcab775548..10531d23d8 100644 --- a/tgui/packages/tgui-panel/ping/reducer.ts +++ b/tgui/packages/tgui-panel/ping/reducer.ts @@ -5,8 +5,13 @@ */ import { clamp01, scale } from 'common/math'; + import { pingFail, pingSuccess } from './actions'; -import { PING_MAX_FAILS, PING_ROUNDTRIP_BEST, PING_ROUNDTRIP_WORST } from './constants'; +import { + PING_MAX_FAILS, + PING_ROUNDTRIP_BEST, + PING_ROUNDTRIP_WORST, +} from './constants'; type PingState = { roundtrip: number | undefined; @@ -35,7 +40,7 @@ export const pingReducer = (state = {} as PingState, action) => { if (type === pingFail.type) { const { failCount = 0 } = state; const networkQuality = clamp01( - state.networkQuality - failCount / PING_MAX_FAILS + state.networkQuality - failCount / PING_MAX_FAILS, ); const nextState: PingState = { ...state, diff --git a/tgui/packages/tgui-panel/reconnect.tsx b/tgui/packages/tgui-panel/reconnect.tsx index 589bc7f110..69bab020bd 100644 --- a/tgui/packages/tgui-panel/reconnect.tsx +++ b/tgui/packages/tgui-panel/reconnect.tsx @@ -1,5 +1,6 @@ +import { useDispatch } from 'tgui/backend'; import { Button } from 'tgui/components'; -import { useDispatch } from 'common/redux'; + import { dismissWarning } from './game/actions'; let url: string | null = null; @@ -13,18 +14,19 @@ setInterval(() => { }); }, 5000); -export const ReconnectButton = (props, context) => { +export const ReconnectButton = (props) => { if (!url) { return null; } - const dispatch = useDispatch(context); + const dispatch = useDispatch(); return ( <> diff --git a/tgui/packages/tgui-panel/settings/SettingsPanel.jsx b/tgui/packages/tgui-panel/settings/SettingsPanel.jsx index 7dd1478ed1..f8d4c88983 100644 --- a/tgui/packages/tgui-panel/settings/SettingsPanel.jsx +++ b/tgui/packages/tgui-panel/settings/SettingsPanel.jsx @@ -5,19 +5,53 @@ */ 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 { ChatPageSettings } from '../chat'; -import { rebuildChat, saveChatToDisk, purgeChatMessageArchive } from '../chat/actions'; -import { THEMES } from '../themes'; -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 { useState } from 'react'; +import { useDispatch, useSelector } from 'tgui/backend'; +import { + Box, + Button, + Collapsible, + ColorBox, + Divider, + Dropdown, + Flex, + Input, + LabeledList, + NumberInput, + Section, + Stack, + Tabs, + TextArea, +} from 'tgui/components'; -export const SettingsPanel = (props, context) => { - const activeTab = useSelector(context, selectActiveTab); - const dispatch = useDispatch(context); +import { ChatPageSettings } from '../chat'; +import { + purgeChatMessageArchive, + rebuildChat, + saveChatToDisk, +} from '../chat/actions'; +import { MESSAGE_TYPES } from '../chat/constants'; +import { useGame } from '../game'; +import { THEMES } from '../themes'; +import { + addHighlightSetting, + changeSettingsTab, + removeHighlightSetting, + updateHighlightSetting, + updateSettings, + updateToggle, +} from './actions'; +import { FONTS, MAX_HIGHLIGHT_SETTINGS, SETTINGS_TABS } from './constants'; +import { + selectActiveTab, + selectHighlightSettingById, + selectHighlightSettings, + selectSettings, +} from './selectors'; + +export const SettingsPanel = (props) => { + const activeTab = useSelector(selectActiveTab); + const dispatch = useDispatch(); return ( @@ -31,9 +65,10 @@ export const SettingsPanel = (props, context) => { dispatch( changeSettingsTab({ tabId: tab.id, - }) + }), ) - }> + } + > {tab.name} ))} @@ -51,11 +86,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] = useState(false); return (
@@ -67,7 +102,7 @@ export const SettingsGeneral = (props, context) => { dispatch( updateSettings({ theme: value, - }) + }), ) } /> @@ -83,7 +118,7 @@ export const SettingsGeneral = (props, context) => { dispatch( updateSettings({ fontFamily: value, - }) + }), ) } /> @@ -94,7 +129,7 @@ export const SettingsGeneral = (props, context) => { dispatch( updateSettings({ fontFamily: value, - }) + }), ) } /> @@ -127,7 +162,7 @@ export const SettingsGeneral = (props, context) => { dispatch( updateSettings({ fontSize: value, - }) + }), ) } /> @@ -145,7 +180,7 @@ export const SettingsGeneral = (props, context) => { dispatch( updateSettings({ lineHeight: value, - }) + }), ) } /> @@ -160,7 +195,7 @@ export const SettingsGeneral = (props, context) => { dispatch( updateSettings({ showReconnectWarning: !showReconnectWarning, - }) + }), ) } /> @@ -170,14 +205,15 @@ 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); + saveInterval, + } = useSelector(selectSettings); return (
@@ -194,17 +230,25 @@ export const MessageLimits = (props, context) => { dispatch( updateSettings({ visibleMessageLimit: value, - }) + }), ) } /> +   + {visibleMessageLimit >= 5000 ? ( + + Impacts performance! + + ) : ( + '' + )} - + toFixed(value)} @@ -212,12 +256,20 @@ export const MessageLimits = (props, context) => { dispatch( updateSettings({ persistentMessageLimit: value, - }) + }), ) } /> +   + {persistentMessageLimit >= 2500 ? ( + + Delays initialization! + + ) : ( + '' + )} - + { dispatch( updateSettings({ combineMessageLimit: value, - }) + }), ) } /> @@ -249,52 +301,244 @@ export const MessageLimits = (props, context) => { dispatch( updateSettings({ combineIntervalLimit: value, - }) + }), ) } /> + + toFixed(value)} + onDrag={(e, value) => + dispatch( + updateSettings({ + saveInterval: value, + }), + ) + } + /> +   + {saveInterval <= 3 ? ( + + Warning, experimental! Might crash! + + ) : ( + '' + )} +
); }; -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 game = useGame(); + const { + storedRounds, + exportStart, + exportEnd, + logRetainRounds, + logEnable, + logLineCount, + logLimit, + totalStoredMessages, + storedTypes, + } = useSelector(selectSettings); + const [purgeConfirm, setPurgeConfirm] = useState(0); + const [logConfirm, setLogConfirm] = useState(0); return (
- - {/* FIXME: Implement this later on - - + + {logEnable ? ( + logConfirm ? ( + + ) : ( + + ) + ) : ( + + )} + + Round ID:  + + {game.roundId ? game.roundId : 'ERROR'} + + + {logEnable ? ( + <> + + + toFixed(value)} + onDrag={(e, value) => + dispatch( + updateSettings({ + logRetainRounds: value, + }), + ) + } + /> +   + {logRetainRounds > 3 ? ( + + Warning, might crash! + + ) : ( + '' + )} + + + toFixed(value)} + onDrag={(e, value) => + dispatch( + updateSettings({ + logLimit: value, + }), + ) + } + /> +   + {logLimit > 0 ? ( + 10000 ? 'red' : 'label'} + > + {logLimit > 15000 + ? 'Warning, might crash! Takes priority above round retention.' + : 'Takes priority above round retention.'} + + ) : ( + '' + )} + + +
+ + {MESSAGE_TYPES.map((typeDef) => ( + + dispatch( + updateToggle({ + type: typeDef.type, + }), + ) + } + > + {typeDef.name} + + ))} + +
+ + ) : ( + '' + )} + + + toFixed(value)} + onDrag={(e, value) => + dispatch( + updateSettings({ + exportStart: value, + }), ) } /> + toFixed(value)} + onDrag={(e, value) => + dispatch( + updateSettings({ + exportEnd: value, + }), + ) + } + /> +   + + Stored Rounds:  + + {storedRounds} - */} - + toFixed(value)} @@ -302,7 +546,7 @@ export const ExportTab = (props, context) => { dispatch( updateSettings({ logLineCount: value, - }) + }), ) } /> @@ -322,7 +566,8 @@ export const ExportTab = (props, context) => { onClick={() => { dispatch(purgeChatMessageArchive()); setPurgeConfirm(2); - }}> + }} + > {purgeConfirm > 1 ? 'Purged!' : 'Are you sure?'} ) : ( @@ -334,7 +579,8 @@ export const ExportTab = (props, context) => { setTimeout(() => { setPurgeConfirm(false); }, 5000); - }}> + }} + > Purge message archive )} @@ -342,9 +588,9 @@ export const ExportTab = (props, context) => { ); }; -const TextHighlightSettings = (props, context) => { - const highlightSettings = useSelector(context, selectHighlightSettings); - const dispatch = useDispatch(context); +const TextHighlightSettings = (props) => { + const highlightSettings = useSelector(selectHighlightSettings); + const dispatch = useDispatch(); return (
@@ -383,10 +629,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 +654,7 @@ const TextHighlightSetting = (props, context) => { dispatch( removeHighlightSetting({ id: id, - }) + }), ) } /> @@ -424,7 +670,7 @@ const TextHighlightSetting = (props, context) => { updateHighlightSetting({ id: id, highlightBlacklist: !highlightBlacklist, - }) + }), ) } /> @@ -440,7 +686,7 @@ const TextHighlightSetting = (props, context) => { updateHighlightSetting({ id: id, highlightWholeMessage: !highlightWholeMessage, - }) + }), ) } /> @@ -456,7 +702,7 @@ const TextHighlightSetting = (props, context) => { updateHighlightSetting({ id: id, matchWord: !matchWord, - }) + }), ) } /> @@ -471,7 +717,7 @@ const TextHighlightSetting = (props, context) => { updateHighlightSetting({ id: id, matchCase: !matchCase, - }) + }), ) } /> @@ -488,7 +734,7 @@ const TextHighlightSetting = (props, context) => { updateHighlightSetting({ id: id, highlightColor: value, - }) + }), ) } /> @@ -503,7 +749,7 @@ const TextHighlightSetting = (props, context) => { updateHighlightSetting({ id: id, highlightText: value, - }) + }), ) } /> @@ -517,7 +763,7 @@ const TextHighlightSetting = (props, context) => { updateHighlightSetting({ id: id, blacklistText: value, - }) + }), ) } /> diff --git a/tgui/packages/tgui-panel/settings/actions.ts b/tgui/packages/tgui-panel/settings/actions.ts index 1550bef80b..1e94bf5b46 100644 --- a/tgui/packages/tgui-panel/settings/actions.ts +++ b/tgui/packages/tgui-panel/settings/actions.ts @@ -5,8 +5,10 @@ */ import { createAction } from 'common/redux'; + import { createHighlightSetting } from './model'; +export const updateToggle = createAction('settings/updateToggle'); export const updateSettings = createAction('settings/update'); export const loadSettings = createAction('settings/load'); export const changeSettingsTab = createAction('settings/changeTab'); @@ -16,11 +18,11 @@ export const addHighlightSetting = createAction( 'settings/addHighlightSetting', () => ({ payload: createHighlightSetting(), - }) + }), ); export const removeHighlightSetting = createAction( - 'settings/removeHighlightSetting' + 'settings/removeHighlightSetting', ); export const updateHighlightSetting = createAction( - 'settings/updateHighlightSetting' + 'settings/updateHighlightSetting', ); diff --git a/tgui/packages/tgui-panel/settings/hooks.ts b/tgui/packages/tgui-panel/settings/hooks.ts index da46322f9a..b37b49a22c 100644 --- a/tgui/packages/tgui-panel/settings/hooks.ts +++ b/tgui/packages/tgui-panel/settings/hooks.ts @@ -4,13 +4,14 @@ * @license MIT */ -import { useDispatch, useSelector } from 'common/redux'; -import { updateSettings, toggleSettings } from './actions'; +import { useDispatch, useSelector } from 'tgui/backend'; + +import { toggleSettings, updateSettings } from './actions'; import { selectSettings } from './selectors'; -export const useSettings = (context) => { - const settings = useSelector(context, selectSettings); - const dispatch = useDispatch(context); +export const useSettings = () => { + const settings = useSelector(selectSettings); + const dispatch = useDispatch(); return { ...settings, visible: settings.view.visible, diff --git a/tgui/packages/tgui-panel/settings/middleware.js b/tgui/packages/tgui-panel/settings/middleware.js index cef082213d..c2bbe3043c 100644 --- a/tgui/packages/tgui-panel/settings/middleware.js +++ b/tgui/packages/tgui-panel/settings/middleware.js @@ -5,10 +5,18 @@ */ import { storage } from 'common/storage'; + import { setClientTheme } from '../themes'; -import { loadSettings, updateSettings, addHighlightSetting, removeHighlightSetting, updateHighlightSetting } from './actions'; -import { selectSettings } from './selectors'; +import { + addHighlightSetting, + loadSettings, + removeHighlightSetting, + updateHighlightSetting, + updateSettings, + updateToggle, +} from './actions'; import { FONTS_DISABLED } from './constants'; +import { selectSettings } from './selectors'; let overrideRule = null; let overrideFontFamily = null; @@ -56,6 +64,7 @@ export const settingsMiddleware = (store) => { }); } if ( + type === updateToggle.type || type === updateSettings.type || type === loadSettings.type || type === addHighlightSetting.type || diff --git a/tgui/packages/tgui-panel/settings/reducer.js b/tgui/packages/tgui-panel/settings/reducer.js index af0ca15190..b128a7dd4e 100644 --- a/tgui/packages/tgui-panel/settings/reducer.js +++ b/tgui/packages/tgui-panel/settings/reducer.js @@ -4,9 +4,20 @@ * @license MIT */ -import { changeSettingsTab, loadSettings, openChatSettings, toggleSettings, updateSettings, addHighlightSetting, removeHighlightSetting, updateHighlightSetting } from './actions'; +import { MESSAGE_TYPES } from '../chat/constants'; +import { + addHighlightSetting, + changeSettingsTab, + loadSettings, + openChatSettings, + removeHighlightSetting, + toggleSettings, + updateHighlightSetting, + updateSettings, + updateToggle, +} from './actions'; +import { FONTS, MAX_HIGHLIGHT_SETTINGS, SETTINGS_TABS } from './constants'; import { createDefaultHighlightSetting } from './model'; -import { SETTINGS_TABS, FONTS, MAX_HIGHLIGHT_SETTINGS } from './constants'; const defaultHighlightSetting = createDefaultHighlightSetting(); @@ -32,11 +43,20 @@ const initialState = { showReconnectWarning: true, visibleMessageLimit: 2500, persistentMessageLimit: 1000, + saveInterval: 10, combineMessageLimit: 5, combineIntervalLimit: 5, totalStoredMessages: 0, - logRetainDays: -1, - logLineCount: -1, + logRetainRounds: 2, + logEnable: true, + logLineCount: 0, + logLimit: 10000, + storedRounds: 0, + exportStart: 0, + exportEnd: 0, + lastId: null, + initialized: false, + storedTypes: {}, }; export const settingsReducer = (state = initialState, action) => { @@ -47,6 +67,14 @@ export const settingsReducer = (state = initialState, action) => { ...payload, }; } + if (type === updateToggle.type) { + const { type } = payload; + state.storedTypes[type] = !state.storedTypes[type]; + return { + ...state, + storedTypes: { ...state.storedTypes }, + }; + } if (type === loadSettings.type) { // Validate version and/or migrate state if (!payload?.version) { @@ -58,6 +86,19 @@ export const settingsReducer = (state = initialState, action) => { ...state, ...payload, }; + nextState.initialized = true; + let newFilters = {}; + for (let typeDef of MESSAGE_TYPES) { + if ( + nextState.storedTypes[typeDef.type] === null || + nextState.storedTypes[typeDef.type] === undefined + ) { + newFilters[typeDef.type] = true; + } else { + newFilters[typeDef.type] = nextState.storedTypes[typeDef.type]; + } + } + nextState.storedTypes = newFilters; // Lazy init the list for compatibility reasons if (!nextState.highlightSettings) { nextState.highlightSettings = [defaultHighlightSetting.id]; @@ -139,7 +180,7 @@ export const settingsReducer = (state = initialState, action) => { } else { delete nextState.highlightSettingById[id]; nextState.highlightSettings = nextState.highlightSettings.filter( - (sid) => sid !== id + (sid) => sid !== id, ); if (!nextState.highlightSettings.length) { nextState.highlightSettings.push(defaultHighlightSetting.id); diff --git a/tgui/packages/tgui-panel/styles/main.scss b/tgui/packages/tgui-panel/styles/main.scss index ee96a9c5a6..08e60d18ee 100644 --- a/tgui/packages/tgui-panel/styles/main.scss +++ b/tgui/packages/tgui-panel/styles/main.scss @@ -10,7 +10,7 @@ @use '~tgui/styles/base.scss' with ( $color-bg: #202020, $color-bg-section: color.adjust(#202020, $lightness: -5%), - $color-bg-grad-spread: 0%, + $color-bg-grad-spread: 0% ); // Core styles diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss index 91c00a156a..04d52c1f9d 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-dark.scss @@ -375,7 +375,12 @@ img.icon.bigicon { .infoplain, .oocplain, .warningplain, -.chatexport { +.chatexpor, +.body { +} + +.hivemind { + font-style: italic; } .nif { @@ -396,6 +401,7 @@ img.icon.bigicon { } .binarysay { + font-style: italic; color: #1e90ff; } diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss index 1c613c8461..7d724436a7 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-light.scss @@ -393,7 +393,12 @@ img.icon.bigicon { .infoplain, .oocplain, .warningplain, -.chatexport { +.chatexport, +.body { +} + +.hivemind { + font-style: italic; } .nif { @@ -414,9 +419,10 @@ img.icon.bigicon { } .binarysay { + font-style: italic; color: #20c20e; background-color: #000000; - display: block; + display: inline-block; } .binarysay a { diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-vchatdark.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-vchatdark.scss index 99817f1b2a..0666c15b1f 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-vchatdark.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-vchatdark.scss @@ -375,7 +375,12 @@ img.icon.bigicon { .infoplain, .oocplain, .warningplain, -.chatexport { +.chatexport, +.body { +} + +.hivemind { + font-style: italic; color: #ffffff; } @@ -397,7 +402,8 @@ img.icon.bigicon { } .binarysay { - color: #1e90ff; + font-style: italic; + color: #ffffff; } .binarysay a { diff --git a/tgui/packages/tgui-panel/styles/tgchat/chat-vchatlight.scss b/tgui/packages/tgui-panel/styles/tgchat/chat-vchatlight.scss index 9ea1f659c0..217ba9a6f2 100644 --- a/tgui/packages/tgui-panel/styles/tgchat/chat-vchatlight.scss +++ b/tgui/packages/tgui-panel/styles/tgchat/chat-vchatlight.scss @@ -393,8 +393,12 @@ img.icon.bigicon { .infoplain, .oocplain, .warningplain, -.chatexport { - color: #ffffff; +.chatexport, +.body { +} + +.hivemind { + font-style: italic; } .nif { @@ -415,9 +419,8 @@ img.icon.bigicon { } .binarysay { - color: #20c20e; - background-color: #000000; - display: block; + font-style: italic; + color: #000000; } .binarysay a { diff --git a/tgui/packages/tgui-panel/styles/themes/light.scss b/tgui/packages/tgui-panel/styles/themes/light.scss index 1f752d51d6..19ddd3642c 100644 --- a/tgui/packages/tgui-panel/styles/themes/light.scss +++ b/tgui/packages/tgui-panel/styles/themes/light.scss @@ -22,7 +22,7 @@ $color-fg: #000000, $color-bg: #eeeeee, $color-bg-section: #ffffff, - $color-bg-grad-spread: 0%, + $color-bg-grad-spread: 0% ); // A fat warning to anyone who wants to use this: this only half works. diff --git a/tgui/packages/tgui-panel/styles/themes/vchatdark.scss b/tgui/packages/tgui-panel/styles/themes/vchatdark.scss index d4fbcd95d4..e78da8f9ed 100644 --- a/tgui/packages/tgui-panel/styles/themes/vchatdark.scss +++ b/tgui/packages/tgui-panel/styles/themes/vchatdark.scss @@ -27,7 +27,10 @@ @include meta.load-css('~tgui/styles/components/ProgressBar.scss'); // Components specific to tgui-panel - @include meta.load-css('../components/Chat.scss'); + @include meta.load-css( + '../components/Chat.scss', + $with: ('text-color': #ffffff) + ); // Layouts @include meta.load-css('~tgui/styles/layouts/Layout.scss'); diff --git a/tgui/packages/tgui-panel/styles/themes/vchatlight.scss b/tgui/packages/tgui-panel/styles/themes/vchatlight.scss index 9acaef59d8..1679f55976 100644 --- a/tgui/packages/tgui-panel/styles/themes/vchatlight.scss +++ b/tgui/packages/tgui-panel/styles/themes/vchatlight.scss @@ -7,23 +7,23 @@ @use 'sass:meta'; @use '~tgui/styles/colors.scss' with ( - $primary: #ffffff, - $bg-lightness: -25%, - $fg-lightness: -10%, - $label: #3b3b3b, - // Makes button look actually grey due to weird maths. - $grey: #ffffff, - // Commenting out color maps will adjust all colors based on the lightness - // settings above, but will add extra 10KB to the theme. - // $fg-map-keys: (), - // $bg-map-keys: (), - ); + $primary: #ffffff, + $bg-lightness: -25%, + $fg-lightness: -10%, + $label: #3b3b3b, + // Makes button look actually grey due to weird maths. + $grey: #ffffff, + // Commenting out color maps will adjust all colors based on the lightness + // settings above, but will add extra 10KB to the theme. + // $fg-map-keys: (), + // $bg-map-keys: (), +); @use '~tgui/styles/base.scss' with ( - $color-fg: #000000, - $color-bg: #eeeeee, - $color-bg-section: #ffffff, - $color-bg-grad-spread: 0%, - ); + $color-fg: #000000, + $color-bg: #eeeeee, + $color-bg-section: #ffffff, + $color-bg-grad-spread: 0% +); // A fat warning to anyone who wants to use this: this only half works. // It was made almost purely for the nuke ui, and requires a good amount of manual hacks to get it working as intended. diff --git a/tgui/packages/tgui-panel/telemetry.js b/tgui/packages/tgui-panel/telemetry.js index bade3c6391..3b1eb9a950 100644 --- a/tgui/packages/tgui-panel/telemetry.js +++ b/tgui/packages/tgui-panel/telemetry.js @@ -58,7 +58,7 @@ export const telemetryMiddleware = (store) => { let telemetryMutated = false; const duplicateConnection = telemetry.connections.find((conn) => - connectionsMatch(conn, client) + connectionsMatch(conn, client), ); if (!duplicateConnection) { telemetryMutated = true; diff --git a/tgui/packages/tgui-polyfill/00-html5shiv.js b/tgui/packages/tgui-polyfill/00-html5shiv.js deleted file mode 100644 index fcde92e6f7..0000000000 --- a/tgui/packages/tgui-polyfill/00-html5shiv.js +++ /dev/null @@ -1,336 +0,0 @@ -/** - * @file - * @copyright 2014 Alexander Farkas - * @license MIT - */ - -/* eslint-disable */ -(function (window, document) { - /*jshint evil:true */ - /** version */ - var version = '3.7.3'; - - /** Preset options */ - var options = window.html5 || {}; - - /** Used to skip problem elements */ - var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; - - /** Not all elements can be cloned in IE **/ - var saveClones = - /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; - - /** Detect whether the browser supports default html5 styles */ - var supportsHtml5Styles; - - /** Name of the expando, to work with multiple documents or to re-shiv one document */ - var expando = '_html5shiv'; - - /** The id for the the documents expando */ - var expanID = 0; - - /** Cached data for each document */ - var expandoData = {}; - - /** Detect whether the browser supports unknown elements */ - var supportsUnknownElements; - - (function () { - try { - var a = document.createElement('a'); - a.innerHTML = ''; - //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles - supportsHtml5Styles = 'hidden' in a; - - supportsUnknownElements = - a.childNodes.length == 1 || - (function () { - // assign a false positive if unable to shiv - document.createElement('a'); - var frag = document.createDocumentFragment(); - return ( - typeof frag.cloneNode == 'undefined' || - typeof frag.createDocumentFragment == 'undefined' || - typeof frag.createElement == 'undefined' - ); - })(); - } catch (e) { - // assign a false positive if detection fails => unable to shiv - supportsHtml5Styles = true; - supportsUnknownElements = true; - } - })(); - - /*--------------------------------------------------------------------------*/ - - /** - * Creates a style sheet with the given CSS text and adds it to the document. - * @private - * @param {Document} ownerDocument The document. - * @param {String} cssText The CSS text. - * @returns {StyleSheet} The style element. - */ - function addStyleSheet(ownerDocument, cssText) { - var p = ownerDocument.createElement('p'), - parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; - - p.innerHTML = 'x'; - return parent.insertBefore(p.lastChild, parent.firstChild); - } - - /** - * Returns the value of `html5.elements` as an array. - * @private - * @returns {Array} An array of shived element node names. - */ - function getElements() { - var elements = html5.elements; - return typeof elements == 'string' ? elements.split(' ') : elements; - } - - /** - * Extends the built-in list of html5 elements - * @memberOf html5 - * @param {String|Array} newElements whitespace separated list or array of new element names to shiv - * @param {Document} ownerDocument The context document. - */ - function addElements(newElements, ownerDocument) { - var elements = html5.elements; - if (typeof elements != 'string') { - elements = elements.join(' '); - } - if (typeof newElements != 'string') { - newElements = newElements.join(' '); - } - html5.elements = elements + ' ' + newElements; - shivDocument(ownerDocument); - } - - /** - * Returns the data associated to the given document - * @private - * @param {Document} ownerDocument The document. - * @returns {Object} An object of data. - */ - function getExpandoData(ownerDocument) { - var data = expandoData[ownerDocument[expando]]; - if (!data) { - data = {}; - expanID++; - ownerDocument[expando] = expanID; - expandoData[expanID] = data; - } - return data; - } - - /** - * returns a shived element for the given nodeName and document - * @memberOf html5 - * @param {String} nodeName name of the element - * @param {Document|DocumentFragment} ownerDocument The context document. - * @returns {Object} The shived element. - */ - function createElement(nodeName, ownerDocument, data) { - if (!ownerDocument) { - ownerDocument = document; - } - if (supportsUnknownElements) { - return ownerDocument.createElement(nodeName); - } - if (!data) { - data = getExpandoData(ownerDocument); - } - var node; - - if (data.cache[nodeName]) { - node = data.cache[nodeName].cloneNode(); - } else if (saveClones.test(nodeName)) { - node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); - } else { - node = data.createElem(nodeName); - } - - // Avoid adding some elements to fragments in IE < 9 because - // * Attributes like `name` or `type` cannot be set/changed once an element - // is inserted into a document/fragment - // * Link elements with `src` attributes that are inaccessible, as with - // a 403 response, will cause the tab/window to crash - // * Script elements appended to fragments will execute when their `src` - // or `text` property is set - return node.canHaveChildren && !reSkip.test(nodeName) && !node.tagUrn ? data.frag.appendChild(node) : node; - } - - /** - * returns a shived DocumentFragment for the given document - * @memberOf html5 - * @param {Document} ownerDocument The context document. - * @returns {Object} The shived DocumentFragment. - */ - function createDocumentFragment(ownerDocument, data) { - if (!ownerDocument) { - ownerDocument = document; - } - if (supportsUnknownElements) { - return ownerDocument.createDocumentFragment(); - } - data = data || getExpandoData(ownerDocument); - var clone = data.frag.cloneNode(), - i = 0, - elems = getElements(), - l = elems.length; - for (; i < l; i++) { - clone.createElement(elems[i]); - } - return clone; - } - - /** - * Shivs the `createElement` and `createDocumentFragment` methods of the document. - * @private - * @param {Document|DocumentFragment} ownerDocument The document. - * @param {Object} data of the document. - */ - function shivMethods(ownerDocument, data) { - if (!data.cache) { - data.cache = {}; - data.createElem = ownerDocument.createElement; - data.createFrag = ownerDocument.createDocumentFragment; - data.frag = data.createFrag(); - } - - ownerDocument.createElement = function (nodeName) { - //abort shiv - if (!html5.shivMethods) { - return data.createElem(nodeName); - } - return createElement(nodeName, ownerDocument, data); - }; - - ownerDocument.createDocumentFragment = Function( - 'h,f', - 'return function(){' + - 'var n=f.cloneNode(),c=n.createElement;' + - 'h.shivMethods&&(' + - // unroll the `createElement` calls - getElements() - .join() - .replace(/[\w\-:]+/g, function (nodeName) { - data.createElem(nodeName); - data.frag.createElement(nodeName); - return 'c("' + nodeName + '")'; - }) + - ');return n}' - )(html5, data.frag); - } - - /*--------------------------------------------------------------------------*/ - - /** - * Shivs the given document. - * @memberOf html5 - * @param {Document} ownerDocument The document to shiv. - * @returns {Document} The shived document. - */ - function shivDocument(ownerDocument) { - if (!ownerDocument) { - ownerDocument = document; - } - var data = getExpandoData(ownerDocument); - - if (html5.shivCSS && !supportsHtml5Styles && !data.hasCSS) { - data.hasCSS = !!addStyleSheet( - ownerDocument, - // corrects block display not defined in IE6/7/8/9 - 'article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}' + - // adds styling not present in IE6/7/8/9 - 'mark{background:#FF0;color:#000}' + - // hides non-rendered elements - 'template{display:none}' - ); - } - if (!supportsUnknownElements) { - shivMethods(ownerDocument, data); - } - return ownerDocument; - } - - /*--------------------------------------------------------------------------*/ - - /** - * The `html5` object is exposed so that more elements can be shived and - * existing shiving can be detected on iframes. - * @type Object - * @example - * - * // options can be changed before the script is included - * html5 = { 'elements': 'mark section', 'shivCSS': false, 'shivMethods': false }; - */ - var html5 = { - /** - * An array or space separated string of node names of the elements to shiv. - * @memberOf html5 - * @type Array|String - */ - 'elements': - options.elements || - 'abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video', - - /** - * current version of html5shiv - */ - 'version': version, - - /** - * A flag to indicate that the HTML5 style sheet should be inserted. - * @memberOf html5 - * @type Boolean - */ - 'shivCSS': options.shivCSS !== false, - - /** - * Is equal to true if a browser supports creating unknown/HTML5 elements - * @memberOf html5 - * @type boolean - */ - 'supportsUnknownElements': supportsUnknownElements, - - /** - * A flag to indicate that the document's `createElement` and `createDocumentFragment` - * methods should be overwritten. - * @memberOf html5 - * @type Boolean - */ - 'shivMethods': options.shivMethods !== false, - - /** - * A string to describe the type of `html5` object ("default" or "default print"). - * @memberOf html5 - * @type String - */ - 'type': 'default', - - // shivs the document according to the specified `html5` object options - 'shivDocument': shivDocument, - - //creates a shived element - createElement: createElement, - - //creates a shived documentFragment - createDocumentFragment: createDocumentFragment, - - //extends list of elements - addElements: addElements, - }; - - /*--------------------------------------------------------------------------*/ - - // expose html5 - window.html5 = html5; - - // shiv the document - shivDocument(document); - - if (typeof module == 'object' && module.exports) { - module.exports = html5; - } -})(window, document); diff --git a/tgui/packages/tgui-polyfill/01-ie8.js b/tgui/packages/tgui-polyfill/01-ie8.js deleted file mode 100644 index 379b335562..0000000000 --- a/tgui/packages/tgui-polyfill/01-ie8.js +++ /dev/null @@ -1,773 +0,0 @@ -/** - * @file - * @copyright 2013 Andrea Giammarchi, WebReflection - * @license MIT - */ - -/* eslint-disable */ -(function (window) { - /*! (C) WebReflection Mit Style License */ - if (document.createEvent) return; - var DUNNOABOUTDOMLOADED = true, - READYEVENTDISPATCHED = false, - ONREADYSTATECHANGE = 'onreadystatechange', - DOMCONTENTLOADED = 'DOMContentLoaded', - SECRET = '__IE8__' + Math.random(), - // Object = window.Object, - defineProperty = - Object.defineProperty || - // just in case ... - function (object, property, descriptor) { - object[property] = descriptor.value; - }, - defineProperties = - Object.defineProperties || - // IE8 implemented defineProperty but not the plural... - function (object, descriptors) { - for (var key in descriptors) { - if (hasOwnProperty.call(descriptors, key)) { - try { - defineProperty(object, key, descriptors[key]); - } catch (o_O) { - if (window.console) { - console.log(key + ' failed on object:', object, o_O.message); - } - } - } - } - }, - getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor, - hasOwnProperty = Object.prototype.hasOwnProperty, - // here IE7 will break like a charm - ElementPrototype = window.Element.prototype, - TextPrototype = window.Text.prototype, - // none of above native constructors exist/are exposed - possiblyNativeEvent = /^[a-z]+$/, - // ^ actually could probably be just /^[a-z]+$/ - readyStateOK = /loaded|complete/, - types = {}, - div = document.createElement('div'), - html = document.documentElement, - removeAttribute = html.removeAttribute, - setAttribute = html.setAttribute, - valueDesc = function (value) { - return { - enumerable: true, - writable: true, - configurable: true, - value: value, - }; - }; - function commonEventLoop(currentTarget, e, $handlers, synthetic) { - for ( - var handler, - continuePropagation, - handlers = $handlers.slice(), - evt = enrich(e, currentTarget), - i = 0, - length = handlers.length; - i < length; - i++ - ) { - handler = handlers[i]; - if (typeof handler === 'object') { - if (typeof handler.handleEvent === 'function') { - handler.handleEvent(evt); - } - } else { - handler.call(currentTarget, evt); - } - if (evt.stoppedImmediatePropagation) break; - } - continuePropagation = !evt.stoppedPropagation; - /* - if (continuePropagation && !synthetic && !live(currentTarget)) { - evt.cancelBubble = true; - } - */ - return synthetic && continuePropagation && currentTarget.parentNode - ? currentTarget.parentNode.dispatchEvent(evt) - : !evt.defaultPrevented; - } - - function commonDescriptor(get, set) { - return { - // if you try with enumerable: true - // IE8 will miserably fail - configurable: true, - get: get, - set: set, - }; - } - - function commonTextContent(protoDest, protoSource, property) { - var descriptor = getOwnPropertyDescriptor(protoSource || protoDest, property); - defineProperty( - protoDest, - 'textContent', - commonDescriptor( - function () { - return descriptor.get.call(this); - }, - function (textContent) { - descriptor.set.call(this, textContent); - } - ) - ); - } - - function enrich(e, currentTarget) { - e.currentTarget = currentTarget; - e.eventPhase = - // AT_TARGET : BUBBLING_PHASE - e.target === e.currentTarget ? 2 : 3; - return e; - } - - function find(array, value) { - var i = array.length; - while (i-- && array[i] !== value); - return i; - } - - function getTextContent() { - if (this.tagName === 'BR') return '\n'; - var textNode = this.firstChild, - arrayContent = []; - while (textNode) { - if (textNode.nodeType !== 8 && textNode.nodeType !== 7) { - arrayContent.push(textNode.textContent); - } - textNode = textNode.nextSibling; - } - return arrayContent.join(''); - } - - function live(self) { - return self.nodeType !== 9 && html.contains(self); - } - - function onkeyup(e) { - var evt = document.createEvent('Event'); - evt.initEvent('input', true, true); - (e.srcElement || e.fromElement || document).dispatchEvent(evt); - } - - function onReadyState(e) { - if (!READYEVENTDISPATCHED && readyStateOK.test(document.readyState)) { - READYEVENTDISPATCHED = !READYEVENTDISPATCHED; - document.detachEvent(ONREADYSTATECHANGE, onReadyState); - e = document.createEvent('Event'); - e.initEvent(DOMCONTENTLOADED, true, true); - document.dispatchEvent(e); - } - } - - function getter(attr) { - return function () { - return html[attr] || (document.body && document.body[attr]) || 0; - }; - } - - function setTextContent(textContent) { - var node; - while ((node = this.lastChild)) { - this.removeChild(node); - } - /*jshint eqnull:true */ - if (textContent != null) { - this.appendChild(document.createTextNode(textContent)); - } - } - - function verify(self, e) { - if (!e) { - e = window.event; - } - if (!e.target) { - e.target = e.srcElement || e.fromElement || document; - } - if (!e.timeStamp) { - e.timeStamp = new Date().getTime(); - } - return e; - } - - // normalized textContent for: - // comment, script, style, text, title - commonTextContent(window.HTMLCommentElement.prototype, ElementPrototype, 'nodeValue'); - - commonTextContent(window.HTMLScriptElement.prototype, null, 'text'); - - commonTextContent(TextPrototype, null, 'nodeValue'); - - commonTextContent(window.HTMLTitleElement.prototype, null, 'text'); - - defineProperty( - window.HTMLStyleElement.prototype, - 'textContent', - (function (descriptor) { - return commonDescriptor( - function () { - return descriptor.get.call(this.styleSheet); - }, - function (textContent) { - descriptor.set.call(this.styleSheet, textContent); - } - ); - })(getOwnPropertyDescriptor(window.CSSStyleSheet.prototype, 'cssText')) - ); - - var opacityre = /\b\s*alpha\s*\(\s*opacity\s*=\s*(\d+)\s*\)/; - defineProperty(window.CSSStyleDeclaration.prototype, 'opacity', { - get: function () { - var m = this.filter.match(opacityre); - return m ? (m[1] / 100).toString() : ''; - }, - set: function (value) { - this.zoom = 1; - var found = false; - if (value < 1) { - value = ' alpha(opacity=' + Math.round(value * 100) + ')'; - } else { - value = ''; - } - this.filter = this.filter.replace(opacityre, function () { - found = true; - return value; - }); - if (!found && value) { - this.filter += value; - } - }, - }); - - defineProperties(ElementPrototype, { - // bonus - textContent: { - get: getTextContent, - set: setTextContent, - }, - // http://www.w3.org/TR/ElementTraversal/#interface-elementTraversal - firstElementChild: { - get: function () { - for (var childNodes = this.childNodes || [], i = 0, length = childNodes.length; i < length; i++) { - if (childNodes[i].nodeType == 1) return childNodes[i]; - } - }, - }, - lastElementChild: { - get: function () { - for (var childNodes = this.childNodes || [], i = childNodes.length; i--; ) { - if (childNodes[i].nodeType == 1) return childNodes[i]; - } - }, - }, - oninput: { - get: function () { - return this._oninput || null; - }, - set: function (oninput) { - if (this._oninput) { - this.removeEventListener('input', this._oninput); - this._oninput = oninput; - if (oninput) { - this.addEventListener('input', oninput); - } - } - }, - }, - previousElementSibling: { - get: function () { - var previousElementSibling = this.previousSibling; - while (previousElementSibling && previousElementSibling.nodeType != 1) { - previousElementSibling = previousElementSibling.previousSibling; - } - return previousElementSibling; - }, - }, - nextElementSibling: { - get: function () { - var nextElementSibling = this.nextSibling; - while (nextElementSibling && nextElementSibling.nodeType != 1) { - nextElementSibling = nextElementSibling.nextSibling; - } - return nextElementSibling; - }, - }, - childElementCount: { - get: function () { - for ( - var count = 0, childNodes = this.childNodes || [], i = childNodes.length; - i--; - count += childNodes[i].nodeType == 1 - ); - return count; - }, - }, - /* - // children would be an override - // IE8 already supports them but with comments too - // not just nodeType 1 - children: { - get: function () { - for(var - children = [], - childNodes = this.childNodes || [], - i = 0, length = childNodes.length; - i < length; i++ - ) { - if (childNodes[i].nodeType == 1) { - children.push(childNodes[i]); - } - } - return children; - } - }, - */ - // DOM Level 2 EventTarget methods and events - addEventListener: valueDesc(function (type, handler, capture) { - if (typeof handler !== 'function' && typeof handler !== 'object') return; - var self = this, - ontype = 'on' + type, - temple = self[SECRET] || defineProperty(self, SECRET, { value: {} })[SECRET], - currentType = temple[ontype] || (temple[ontype] = {}), - handlers = currentType.h || (currentType.h = []), - e, - attr; - if (!hasOwnProperty.call(currentType, 'w')) { - currentType.w = function (e) { - // e[SECRET] is a silent notification needed to avoid - // fired events during live test - return e[SECRET] || commonEventLoop(self, verify(self, e), handlers, false); - }; - // if not detected yet - if (!hasOwnProperty.call(types, ontype)) { - // and potentially a native event - if (possiblyNativeEvent.test(type)) { - // do this heavy thing - try { - // TODO: should I consider tagName too so that - // INPUT[ontype] could be different ? - e = document.createEventObject(); - // do not clone ever a node - // specially a document one ... - // use the secret to ignore them all - e[SECRET] = true; - // document a part if a node has never been - // added to any other node, fireEvent might - // behave very weirdly (read: trigger unspecified errors) - if (self.nodeType != 9) { - /*jshint eqnull:true */ - if (self.parentNode == null) { - div.appendChild(self); - } - if ((attr = self.getAttribute(ontype))) { - removeAttribute.call(self, ontype); - } - } - self.fireEvent(ontype, e); - types[ontype] = true; - } catch (meh) { - types[ontype] = false; - while (div.hasChildNodes()) { - div.removeChild(div.firstChild); - } - } - if (attr != null) { - setAttribute.call(self, ontype, attr); - } - } else { - // no need to bother since - // 'x-event' ain't native for sure - types[ontype] = false; - } - } - if ((currentType.n = types[ontype])) { - self.attachEvent(ontype, currentType.w); - } - } - if (find(handlers, handler) < 0) { - handlers[capture ? 'unshift' : 'push'](handler); - } - if (type === 'input') { - self.attachEvent('onkeyup', onkeyup); - } - }), - dispatchEvent: valueDesc(function (e) { - var self = this, - ontype = 'on' + e.type, - temple = self[SECRET], - currentType = temple && temple[ontype], - valid = !!currentType, - parentNode; - if (!e.target) e.target = self; - return ( - valid - ? currentType.n /* && live(self) */ - ? self.fireEvent(ontype, e) - : commonEventLoop(self, e, currentType.h, true) - : (parentNode = self.parentNode) /* && live(self) */ - ? parentNode.dispatchEvent(e) - : true, - !e.defaultPrevented - ); - }), - removeEventListener: valueDesc(function (type, handler, capture) { - if (typeof handler !== 'function' && typeof handler !== 'object') return; - var self = this, - ontype = 'on' + type, - temple = self[SECRET], - currentType = temple && temple[ontype], - handlers = currentType && currentType.h, - i = handlers ? find(handlers, handler) : -1; - if (-1 < i) handlers.splice(i, 1); - }), - }); - - /* this is not needed in IE8 - defineProperties(window.HTMLSelectElement.prototype, { - value: { - get: function () { - return this.options[this.selectedIndex].value; - } - } - }); - //*/ - - // EventTarget methods for Text nodes too - defineProperties(TextPrototype, { - addEventListener: valueDesc(ElementPrototype.addEventListener), - dispatchEvent: valueDesc(ElementPrototype.dispatchEvent), - removeEventListener: valueDesc(ElementPrototype.removeEventListener), - }); - - defineProperties(window.XMLHttpRequest.prototype, { - addEventListener: valueDesc(function (type, handler, capture) { - var self = this, - ontype = 'on' + type, - temple = self[SECRET] || defineProperty(self, SECRET, { value: {} })[SECRET], - currentType = temple[ontype] || (temple[ontype] = {}), - handlers = currentType.h || (currentType.h = []); - if (find(handlers, handler) < 0) { - if (!self[ontype]) { - self[ontype] = function () { - var e = document.createEvent('Event'); - e.initEvent(type, true, true); - self.dispatchEvent(e); - }; - } - handlers[capture ? 'unshift' : 'push'](handler); - } - }), - dispatchEvent: valueDesc(function (e) { - var self = this, - ontype = 'on' + e.type, - temple = self[SECRET], - currentType = temple && temple[ontype], - valid = !!currentType; - return ( - valid && - (currentType.n /* && live(self) */ ? self.fireEvent(ontype, e) : commonEventLoop(self, e, currentType.h, true)) - ); - }), - removeEventListener: valueDesc(ElementPrototype.removeEventListener), - }); - - var buttonGetter = getOwnPropertyDescriptor(Event.prototype, 'button').get; - defineProperties(window.Event.prototype, { - bubbles: valueDesc(true), - cancelable: valueDesc(true), - preventDefault: valueDesc(function () { - if (this.cancelable) { - this.returnValue = false; - } - }), - stopPropagation: valueDesc(function () { - this.stoppedPropagation = true; - this.cancelBubble = true; - }), - stopImmediatePropagation: valueDesc(function () { - this.stoppedImmediatePropagation = true; - this.stopPropagation(); - }), - initEvent: valueDesc(function (type, bubbles, cancelable) { - this.type = type; - this.bubbles = !!bubbles; - this.cancelable = !!cancelable; - if (!this.bubbles) { - this.stopPropagation(); - } - }), - pageX: { - get: function () { - return this._pageX || (this._pageX = this.clientX + window.scrollX - (html.clientLeft || 0)); - }, - }, - pageY: { - get: function () { - return this._pageY || (this._pageY = this.clientY + window.scrollY - (html.clientTop || 0)); - }, - }, - which: { - get: function () { - return this.keyCode ? this.keyCode : isNaN(this.button) ? undefined : this.button + 1; - }, - }, - charCode: { - get: function () { - return this.keyCode && this.type == 'keypress' ? this.keyCode : 0; - }, - }, - buttons: { - get: function () { - return buttonGetter.call(this); - }, - }, - button: { - get: function () { - var buttons = this.buttons; - return buttons & 1 ? 0 : buttons & 2 ? 2 : buttons & 4 ? 1 : undefined; - }, - }, - defaultPrevented: { - get: function () { - // if preventDefault() was never called, or returnValue not given a value - // then returnValue is undefined - var returnValue = this.returnValue, - undef; - return !(returnValue === undef || returnValue); - }, - }, - relatedTarget: { - get: function () { - var type = this.type; - if (type === 'mouseover') { - return this.fromElement; - } else if (type === 'mouseout') { - return this.toElement; - } else { - return null; - } - }, - }, - }); - - defineProperties(window.HTMLDocument.prototype, { - defaultView: { - get: function () { - return this.parentWindow; - }, - }, - textContent: { - get: function () { - return this.nodeType === 11 ? getTextContent.call(this) : null; - }, - set: function (textContent) { - if (this.nodeType === 11) { - setTextContent.call(this, textContent); - } - }, - }, - addEventListener: valueDesc(function (type, handler, capture) { - var self = this; - ElementPrototype.addEventListener.call(self, type, handler, capture); - // NOTE: it won't fire if already loaded, this is NOT a $.ready() shim! - // this behaves just like standard browsers - if (DUNNOABOUTDOMLOADED && type === DOMCONTENTLOADED && !readyStateOK.test(self.readyState)) { - DUNNOABOUTDOMLOADED = false; - self.attachEvent(ONREADYSTATECHANGE, onReadyState); - /* global top */ - if (window == top) { - (function gonna(e) { - try { - self.documentElement.doScroll('left'); - onReadyState(); - } catch (o_O) { - setTimeout(gonna, 50); - } - })(); - } - } - }), - dispatchEvent: valueDesc(ElementPrototype.dispatchEvent), - removeEventListener: valueDesc(ElementPrototype.removeEventListener), - createEvent: valueDesc(function (Class) { - var e; - if (Class !== 'Event') throw new Error('unsupported ' + Class); - e = document.createEventObject(); - e.timeStamp = new Date().getTime(); - return e; - }), - }); - - defineProperties(window.Window.prototype, { - getComputedStyle: valueDesc( - (function () { - var // partially grabbed from jQuery and Dean's hack - notpixel = /^(?:[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|))(?!px)[a-z%]+$/, - position = /^(top|right|bottom|left)$/, - re = /\-([a-z])/g, - place = function (match, $1) { - return $1.toUpperCase(); - }; - function ComputedStyle(_) { - this._ = _; - } - - ComputedStyle.prototype.getPropertyValue = function (name) { - var el = this._, - style = el.style, - currentStyle = el.currentStyle, - runtimeStyle = el.runtimeStyle, - result, - left, - rtLeft; - if (name == 'opacity') { - return style.opacity || '1'; - } - name = (name === 'float' ? 'style-float' : name).replace(re, place); - result = currentStyle ? currentStyle[name] : style[name]; - if (notpixel.test(result) && !position.test(name)) { - left = style.left; - rtLeft = runtimeStyle && runtimeStyle.left; - if (rtLeft) { - runtimeStyle.left = currentStyle.left; - } - style.left = name === 'fontSize' ? '1em' : result; - result = style.pixelLeft + 'px'; - style.left = left; - if (rtLeft) { - runtimeStyle.left = rtLeft; - } - } - /*jshint eqnull:true */ - return result == null ? result : result + '' || 'auto'; - }; - - // unsupported - function PseudoComputedStyle() {} - PseudoComputedStyle.prototype.getPropertyValue = function () { - return null; - }; - - return function (el, pseudo) { - return pseudo ? new PseudoComputedStyle(el) : new ComputedStyle(el); - }; - })() - ), - - addEventListener: valueDesc(function (type, handler, capture) { - var self = window, - ontype = 'on' + type, - handlers; - if (!self[ontype]) { - self[ontype] = function (e) { - return commonEventLoop(self, verify(self, e), handlers, false) && undefined; - }; - } - handlers = self[ontype][SECRET] || (self[ontype][SECRET] = []); - if (find(handlers, handler) < 0) { - handlers[capture ? 'unshift' : 'push'](handler); - } - }), - dispatchEvent: valueDesc(function (e) { - var method = window['on' + e.type]; - return method ? method.call(window, e) !== false && !e.defaultPrevented : true; - }), - removeEventListener: valueDesc(function (type, handler, capture) { - var ontype = 'on' + type, - handlers = (window[ontype] || Object)[SECRET], - i = handlers ? find(handlers, handler) : -1; - if (-1 < i) handlers.splice(i, 1); - }), - pageXOffset: { get: getter('scrollLeft') }, - pageYOffset: { get: getter('scrollTop') }, - scrollX: { get: getter('scrollLeft') }, - scrollY: { get: getter('scrollTop') }, - innerWidth: { get: getter('clientWidth') }, - innerHeight: { get: getter('clientHeight') }, - }); - - window.HTMLElement = window.Element; - - (function (styleSheets, HTML5Element, i) { - for (i = 0; i < HTML5Element.length; i++) document.createElement(HTML5Element[i]); - if (!styleSheets.length) document.createStyleSheet(''); - styleSheets[0].addRule(HTML5Element.join(','), 'display:block;'); - })(document.styleSheets, ['header', 'nav', 'section', 'article', 'aside', 'footer']); - - (function () { - if (document.createRange) return; - document.createRange = function createRange() { - return new Range(); - }; - - function getContents(start, end) { - var nodes = [start]; - while (start !== end) { - nodes.push((start = start.nextSibling)); - } - return nodes; - } - - function Range() {} - var proto = Range.prototype; - proto.cloneContents = function cloneContents() { - for ( - var fragment = this._start.ownerDocument.createDocumentFragment(), - nodes = getContents(this._start, this._end), - i = 0, - length = nodes.length; - i < length; - i++ - ) { - fragment.appendChild(nodes[i].cloneNode(true)); - } - return fragment; - }; - proto.cloneRange = function cloneRange() { - var range = new Range(); - range._start = this._start; - range._end = this._end; - return range; - }; - proto.deleteContents = function deleteContents() { - for ( - var parentNode = this._start.parentNode, - nodes = getContents(this._start, this._end), - i = 0, - length = nodes.length; - i < length; - i++ - ) { - parentNode.removeChild(nodes[i]); - } - }; - proto.extractContents = function extractContents() { - for ( - var fragment = this._start.ownerDocument.createDocumentFragment(), - nodes = getContents(this._start, this._end), - i = 0, - length = nodes.length; - i < length; - i++ - ) { - fragment.appendChild(nodes[i]); - } - return fragment; - }; - proto.setEndAfter = function setEndAfter(node) { - this._end = node; - }; - proto.setEndBefore = function setEndBefore(node) { - this._end = node.previousSibling; - }; - proto.setStartAfter = function setStartAfter(node) { - this._start = node.nextSibling; - }; - proto.setStartBefore = function setStartBefore(node) { - this._start = node; - }; - })(); -})(window); diff --git a/tgui/packages/tgui-polyfill/02-dom4.js b/tgui/packages/tgui-polyfill/02-dom4.js deleted file mode 100644 index d9ef036a3e..0000000000 --- a/tgui/packages/tgui-polyfill/02-dom4.js +++ /dev/null @@ -1,912 +0,0 @@ -/** - * @file - * @copyright 2013 Andrea Giammarchi, WebReflection - * @license MIT - */ - -/* eslint-disable */ -(function (window) { - 'use strict'; - /* jshint loopfunc: true, noempty: false*/ - // http://www.w3.org/TR/dom/#element - - function createDocumentFragment() { - return document.createDocumentFragment(); - } - - function createElement(nodeName) { - return document.createElement(nodeName); - } - - function enoughArguments(length, name) { - if (!length) throw new Error('Failed to construct ' + name + ': 1 argument required, but only 0 present.'); - } - - function mutationMacro(nodes) { - if (nodes.length === 1) { - return textNodeIfPrimitive(nodes[0]); - } - for (var fragment = createDocumentFragment(), list = slice.call(nodes), i = 0; i < nodes.length; i++) { - fragment.appendChild(textNodeIfPrimitive(list[i])); - } - return fragment; - } - - function textNodeIfPrimitive(node) { - return typeof node === 'object' ? node : document.createTextNode(node); - } - - for ( - var head, - property, - TemporaryPrototype, - TemporaryTokenList, - wrapVerifyToken, - document = window.document, - hOP = Object.prototype.hasOwnProperty, - defineProperty = - Object.defineProperty || - function (object, property, descriptor) { - if (hOP.call(descriptor, 'value')) { - object[property] = descriptor.value; - } else { - if (hOP.call(descriptor, 'get')) object.__defineGetter__(property, descriptor.get); - if (hOP.call(descriptor, 'set')) object.__defineSetter__(property, descriptor.set); - } - return object; - }, - indexOf = - [].indexOf || - function indexOf(value) { - var length = this.length; - while (length--) { - if (this[length] === value) { - break; - } - } - return length; - }, - // http://www.w3.org/TR/domcore/#domtokenlist - verifyToken = function (token) { - if (!token) { - throw 'SyntaxError'; - } else if (spaces.test(token)) { - throw 'InvalidCharacterError'; - } - return token; - }, - DOMTokenList = function (node) { - var noClassName = typeof node.className === 'undefined', - className = noClassName ? node.getAttribute('class') || '' : node.className, - isSVG = noClassName || typeof className === 'object', - value = (isSVG ? (noClassName ? className : className.baseVal) : className).replace(trim, ''); - if (value.length) { - properties.push.apply(this, value.split(spaces)); - } - this._isSVG = isSVG; - this._ = node; - }, - classListDescriptor = { - get: function get() { - return new DOMTokenList(this); - }, - set: function () {}, - }, - trim = /^\s+|\s+$/g, - spaces = /\s+/, - SPACE = '\x20', - CLASS_LIST = 'classList', - toggle = function toggle(token, force) { - if (this.contains(token)) { - if (!force) { - // force is not true (either false or omitted) - this.remove(token); - } - } else if (force === undefined || force) { - force = true; - this.add(token); - } - return !!force; - }, - DocumentFragmentPrototype = window.DocumentFragment && DocumentFragment.prototype, - Node = window.Node, - NodePrototype = (Node || Element).prototype, - CharacterData = window.CharacterData || Node, - CharacterDataPrototype = CharacterData && CharacterData.prototype, - DocumentType = window.DocumentType, - DocumentTypePrototype = DocumentType && DocumentType.prototype, - ElementPrototype = (window.Element || Node || window.HTMLElement).prototype, - HTMLSelectElement = window.HTMLSelectElement || createElement('select').constructor, - selectRemove = HTMLSelectElement.prototype.remove, - SVGElement = window.SVGElement, - properties = [ - 'matches', - ElementPrototype.matchesSelector || - ElementPrototype.webkitMatchesSelector || - ElementPrototype.khtmlMatchesSelector || - ElementPrototype.mozMatchesSelector || - ElementPrototype.msMatchesSelector || - ElementPrototype.oMatchesSelector || - function matches(selector) { - var parentNode = this.parentNode; - return !!parentNode && -1 < indexOf.call(parentNode.querySelectorAll(selector), this); - }, - 'closest', - function closest(selector) { - var parentNode = this, - matches; - while ( - // document has no .matches - (matches = parentNode && parentNode.matches) && - !parentNode.matches(selector) - ) { - parentNode = parentNode.parentNode; - } - return matches ? parentNode : null; - }, - 'prepend', - function prepend() { - var firstChild = this.firstChild, - node = mutationMacro(arguments); - if (firstChild) { - this.insertBefore(node, firstChild); - } else { - this.appendChild(node); - } - }, - 'append', - function append() { - this.appendChild(mutationMacro(arguments)); - }, - 'before', - function before() { - var parentNode = this.parentNode; - if (parentNode) { - parentNode.insertBefore(mutationMacro(arguments), this); - } - }, - 'after', - function after() { - var parentNode = this.parentNode, - nextSibling = this.nextSibling, - node = mutationMacro(arguments); - if (parentNode) { - if (nextSibling) { - parentNode.insertBefore(node, nextSibling); - } else { - parentNode.appendChild(node); - } - } - }, - // https://dom.spec.whatwg.org/#dom-element-toggleattribute - 'toggleAttribute', - function toggleAttribute(name, force) { - var had = this.hasAttribute(name); - if (1 < arguments.length) { - if (had && !force) this.removeAttribute(name); - else if (force && !had) this.setAttribute(name, ''); - } else if (had) this.removeAttribute(name); - else this.setAttribute(name, ''); - return this.hasAttribute(name); - }, - // WARNING - DEPRECATED - use .replaceWith() instead - 'replace', - function replace() { - this.replaceWith.apply(this, arguments); - }, - 'replaceWith', - function replaceWith() { - var parentNode = this.parentNode; - if (parentNode) { - parentNode.replaceChild(mutationMacro(arguments), this); - } - }, - 'remove', - function remove() { - var parentNode = this.parentNode; - if (parentNode) { - parentNode.removeChild(this); - } - }, - ], - slice = properties.slice, - i = properties.length; - i; - i -= 2 - ) { - property = properties[i - 2]; - if (!(property in ElementPrototype)) { - ElementPrototype[property] = properties[i - 1]; - } - // avoid unnecessary re-patch when the script is included - // gazillion times without any reason whatsoever - // https://github.com/WebReflection/dom4/pull/48 - if (property === 'remove' && !selectRemove._dom4) { - // see https://github.com/WebReflection/dom4/issues/19 - (HTMLSelectElement.prototype[property] = function () { - return 0 < arguments.length ? selectRemove.apply(this, arguments) : ElementPrototype.remove.call(this); - })._dom4 = true; - } - // see https://github.com/WebReflection/dom4/issues/18 - if (/^(?:before|after|replace|replaceWith|remove)$/.test(property)) { - if (CharacterData && !(property in CharacterDataPrototype)) { - CharacterDataPrototype[property] = properties[i - 1]; - } - if (DocumentType && !(property in DocumentTypePrototype)) { - DocumentTypePrototype[property] = properties[i - 1]; - } - } - // see https://github.com/WebReflection/dom4/pull/26 - if (/^(?:append|prepend)$/.test(property)) { - if (DocumentFragmentPrototype) { - if (!(property in DocumentFragmentPrototype)) { - DocumentFragmentPrototype[property] = properties[i - 1]; - } - } else { - try { - createDocumentFragment().constructor.prototype[property] = properties[i - 1]; - } catch (o_O) {} - } - } - } - - // most likely an IE9 only issue - // see https://github.com/WebReflection/dom4/issues/6 - if (!createElement('a').matches('a')) { - ElementPrototype[property] = (function (matches) { - return function (selector) { - return matches.call(this.parentNode ? this : createDocumentFragment().appendChild(this), selector); - }; - })(ElementPrototype[property]); - } - - // used to fix both old webkit and SVG - DOMTokenList.prototype = { - length: 0, - add: function add() { - for (var j = 0, token; j < arguments.length; j++) { - token = arguments[j]; - if (!this.contains(token)) { - properties.push.call(this, property); - } - } - if (this._isSVG) { - this._.setAttribute('class', '' + this); - } else { - this._.className = '' + this; - } - }, - contains: (function (indexOf) { - return function contains(token) { - i = indexOf.call(this, (property = verifyToken(token))); - return -1 < i; - }; - })( - [].indexOf || - function (token) { - i = this.length; - while (i-- && this[i] !== token) {} - return i; - } - ), - item: function item(i) { - return this[i] || null; - }, - remove: function remove() { - for (var j = 0, token; j < arguments.length; j++) { - token = arguments[j]; - if (this.contains(token)) { - properties.splice.call(this, i, 1); - } - } - if (this._isSVG) { - this._.setAttribute('class', '' + this); - } else { - this._.className = '' + this; - } - }, - toggle: toggle, - toString: function toString() { - return properties.join.call(this, SPACE); - }, - }; - - if (SVGElement && !(CLASS_LIST in SVGElement.prototype)) { - defineProperty(SVGElement.prototype, CLASS_LIST, classListDescriptor); - } - - // http://www.w3.org/TR/dom/#domtokenlist - // iOS 5.1 has completely screwed this property - // classList in ElementPrototype is false - // but it's actually there as getter - if (!(CLASS_LIST in document.documentElement)) { - defineProperty(ElementPrototype, CLASS_LIST, classListDescriptor); - } else { - // iOS 5.1 and Nokia ASHA do not support multiple add or remove - // trying to detect and fix that in here - TemporaryTokenList = createElement('div')[CLASS_LIST]; - TemporaryTokenList.add('a', 'b', 'a'); - if ('a\x20b' != TemporaryTokenList) { - // no other way to reach original methods in iOS 5.1 - TemporaryPrototype = TemporaryTokenList.constructor.prototype; - if (!('add' in TemporaryPrototype)) { - // ASHA double fails in here - TemporaryPrototype = window.TemporaryTokenList.prototype; - } - wrapVerifyToken = function (original) { - return function () { - var i = 0; - while (i < arguments.length) { - original.call(this, arguments[i++]); - } - }; - }; - TemporaryPrototype.add = wrapVerifyToken(TemporaryPrototype.add); - TemporaryPrototype.remove = wrapVerifyToken(TemporaryPrototype.remove); - // toggle is broken too ^_^ ... let's fix it - TemporaryPrototype.toggle = toggle; - } - } - - if (!('contains' in NodePrototype)) { - defineProperty(NodePrototype, 'contains', { - value: function (el) { - while (el && el !== this) el = el.parentNode; - return this === el; - }, - }); - } - - if (!('head' in document)) { - defineProperty(document, 'head', { - get: function () { - return head || (head = document.getElementsByTagName('head')[0]); - }, - }); - } - - // requestAnimationFrame partial polyfill - (function () { - for ( - var raf, - rAF = window.requestAnimationFrame, - cAF = window.cancelAnimationFrame, - prefixes = ['o', 'ms', 'moz', 'webkit'], - i = prefixes.length; - !cAF && i--; - - ) { - rAF = rAF || window[prefixes[i] + 'RequestAnimationFrame']; - cAF = window[prefixes[i] + 'CancelAnimationFrame'] || window[prefixes[i] + 'CancelRequestAnimationFrame']; - } - if (!cAF) { - // some FF apparently implemented rAF but no cAF - if (rAF) { - raf = rAF; - rAF = function (callback) { - var goOn = true; - raf(function () { - if (goOn) callback.apply(this, arguments); - }); - return function () { - goOn = false; - }; - }; - cAF = function (id) { - id(); - }; - } else { - rAF = function (callback) { - return setTimeout(callback, 15, 15); - }; - cAF = function (id) { - clearTimeout(id); - }; - } - } - window.requestAnimationFrame = rAF; - window.cancelAnimationFrame = cAF; - })(); - - // http://www.w3.org/TR/dom/#customevent - try { - new window.CustomEvent('?'); - } catch (o_O) { - window.CustomEvent = (function (eventName, defaultInitDict) { - // the infamous substitute - function CustomEvent(type, eventInitDict) { - /*jshint eqnull:true */ - var event = document.createEvent(eventName); - if (typeof type != 'string') { - throw new Error('An event name must be provided'); - } - if (eventName == 'Event') { - event.initCustomEvent = initCustomEvent; - } - if (eventInitDict == null) { - eventInitDict = defaultInitDict; - } - event.initCustomEvent(type, eventInitDict.bubbles, eventInitDict.cancelable, eventInitDict.detail); - return event; - } - - // attached at runtime - function initCustomEvent(type, bubbles, cancelable, detail) { - /*jshint validthis:true*/ - this.initEvent(type, bubbles, cancelable); - this.detail = detail; - } - - // that's it - return CustomEvent; - })( - // is this IE9 or IE10 ? - // where CustomEvent is there - // but not usable as construtor ? - window.CustomEvent - ? // use the CustomEvent interface in such case - 'CustomEvent' - : 'Event', - // otherwise the common compatible one - { - bubbles: false, - cancelable: false, - detail: null, - } - ); - } - - // window.Event as constructor - try { - new Event('_'); - } catch (o_O) { - /* jshint -W022 */ - o_O = (function ($Event) { - function Event(type, init) { - enoughArguments(arguments.length, 'Event'); - var out = document.createEvent('Event'); - if (!init) init = {}; - out.initEvent(type, !!init.bubbles, !!init.cancelable); - return out; - } - Event.prototype = $Event.prototype; - return Event; - })(window.Event || function Event() {}); - defineProperty(window, 'Event', { value: o_O }); - // Android 4 gotcha - if (Event !== o_O) Event = o_O; - } - - // window.KeyboardEvent as constructor - try { - new KeyboardEvent('_', {}); - } catch (o_O) { - /* jshint -W022 */ - o_O = (function ($KeyboardEvent) { - // code inspired by https://gist.github.com/termi/4654819 - var initType = 0, - defaults = { - char: '', - key: '', - location: 0, - ctrlKey: false, - shiftKey: false, - altKey: false, - metaKey: false, - altGraphKey: false, - repeat: false, - locale: navigator.language, - detail: 0, - bubbles: false, - cancelable: false, - keyCode: 0, - charCode: 0, - which: 0, - }, - eventType; - try { - var e = document.createEvent('KeyboardEvent'); - e.initKeyboardEvent('keyup', false, false, window, '+', 3, true, false, true, false, false); - initType = - ((e.keyIdentifier || e.key) == '+' && - (e.keyLocation || e.location) == 3 && - (e.ctrlKey ? (e.altKey ? 1 : 3) : e.shiftKey ? 2 : 4)) || - 9; - } catch (o_O) {} - eventType = 0 < initType ? 'KeyboardEvent' : 'Event'; - - function getModifier(init) { - for ( - var out = [], - keys = [ - 'ctrlKey', - 'Control', - 'shiftKey', - 'Shift', - 'altKey', - 'Alt', - 'metaKey', - 'Meta', - 'altGraphKey', - 'AltGraph', - ], - i = 0; - i < keys.length; - i += 2 - ) { - if (init[keys[i]]) out.push(keys[i + 1]); - } - return out.join(' '); - } - - function withDefaults(target, source) { - for (var key in source) { - if (source.hasOwnProperty(key) && !source.hasOwnProperty.call(target, key)) target[key] = source[key]; - } - return target; - } - - function withInitValues(key, out, init) { - try { - out[key] = init[key]; - } catch (o_O) {} - } - - function KeyboardEvent(type, init) { - enoughArguments(arguments.length, 'KeyboardEvent'); - init = withDefaults(init || {}, defaults); - var out = document.createEvent(eventType), - ctrlKey = init.ctrlKey, - shiftKey = init.shiftKey, - altKey = init.altKey, - metaKey = init.metaKey, - altGraphKey = init.altGraphKey, - modifiers = initType > 3 ? getModifier(init) : null, - key = String(init.key), - chr = String(init.char), - location = init.location, - keyCode = init.keyCode || ((init.keyCode = key) && key.charCodeAt(0)) || 0, - charCode = init.charCode || ((init.charCode = chr) && chr.charCodeAt(0)) || 0, - bubbles = init.bubbles, - cancelable = init.cancelable, - repeat = init.repeat, - locale = init.locale, - view = init.view || window, - args; - if (!init.which) init.which = init.keyCode; - if ('initKeyEvent' in out) { - out.initKeyEvent(type, bubbles, cancelable, view, ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode); - } else if (0 < initType && 'initKeyboardEvent' in out) { - args = [type, bubbles, cancelable, view]; - switch (initType) { - case 1: - args.push(key, location, ctrlKey, shiftKey, altKey, metaKey, altGraphKey); - break; - case 2: - args.push(ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode); - break; - case 3: - args.push(key, location, ctrlKey, altKey, shiftKey, metaKey, altGraphKey); - break; - case 4: - args.push(key, location, modifiers, repeat, locale); - break; - default: - args.push(char, key, location, modifiers, repeat, locale); - } - out.initKeyboardEvent.apply(out, args); - } else { - out.initEvent(type, bubbles, cancelable); - } - for (key in out) { - if (defaults.hasOwnProperty(key) && out[key] !== init[key]) { - withInitValues(key, out, init); - } - } - return out; - } - KeyboardEvent.prototype = $KeyboardEvent.prototype; - return KeyboardEvent; - })(window.KeyboardEvent || function KeyboardEvent() {}); - defineProperty(window, 'KeyboardEvent', { value: o_O }); - // Android 4 gotcha - if (KeyboardEvent !== o_O) KeyboardEvent = o_O; - } - - // window.MouseEvent as constructor - try { - new MouseEvent('_', {}); - } catch (o_O) { - /* jshint -W022 */ - o_O = (function ($MouseEvent) { - function MouseEvent(type, init) { - enoughArguments(arguments.length, 'MouseEvent'); - var out = document.createEvent('MouseEvent'); - if (!init) init = {}; - out.initMouseEvent( - type, - !!init.bubbles, - !!init.cancelable, - init.view || window, - init.detail || 1, - init.screenX || 0, - init.screenY || 0, - init.clientX || 0, - init.clientY || 0, - !!init.ctrlKey, - !!init.altKey, - !!init.shiftKey, - !!init.metaKey, - init.button || 0, - init.relatedTarget || null - ); - return out; - } - MouseEvent.prototype = $MouseEvent.prototype; - return MouseEvent; - })(window.MouseEvent || function MouseEvent() {}); - defineProperty(window, 'MouseEvent', { value: o_O }); - // Android 4 gotcha - if (MouseEvent !== o_O) MouseEvent = o_O; - } - - if (!document.querySelectorAll('*').forEach) { - (function () { - function patch(what) { - var querySelectorAll = what.querySelectorAll; - what.querySelectorAll = function qSA(css) { - var result = querySelectorAll.call(this, css); - result.forEach = Array.prototype.forEach; - return result; - }; - } - patch(document); - patch(Element.prototype); - })(); - } - - try { - // https://drafts.csswg.org/selectors-4/#the-scope-pseudo - document.querySelector(':scope *'); - } catch (o_O) { - (function () { - var dataScope = 'data-scope-' + ((Math.random() * 1e9) >>> 0); - var proto = Element.prototype; - var querySelector = proto.querySelector; - var querySelectorAll = proto.querySelectorAll; - proto.querySelector = function qS(css) { - return find(this, querySelector, css); - }; - proto.querySelectorAll = function qSA(css) { - return find(this, querySelectorAll, css); - }; - function find(node, method, css) { - node.setAttribute(dataScope, null); - var result = method.call( - node, - String(css).replace(/(^|,\s*)(:scope([ >]|$))/g, function ($0, $1, $2, $3) { - return $1 + '[' + dataScope + ']' + ($3 || ' '); - }) - ); - node.removeAttribute(dataScope); - return result; - } - })(); - } -})(window); -(function (global) { - 'use strict'; - - // a WeakMap fallback for DOM nodes only used as key - var DOMMap = - global.WeakMap || - (function () { - var counter = 0, - dispatched = false, - drop = false, - value; - - function dispatch(key, ce, shouldDrop) { - drop = shouldDrop; - dispatched = false; - value = undefined; - key.dispatchEvent(ce); - } - - function Handler(value) { - this.value = value; - } - - Handler.prototype.handleEvent = function handleEvent(e) { - dispatched = true; - if (drop) { - e.currentTarget.removeEventListener(e.type, this, false); - } else { - value = this.value; - } - }; - - function DOMMap() { - counter++; // make id clashing highly improbable - this.__ce__ = new Event('@DOMMap:' + counter + Math.random()); - } - - DOMMap.prototype = { - 'constructor': DOMMap, - 'delete': function del(key) { - return dispatch(key, this.__ce__, true), dispatched; - }, - 'get': function get(key) { - dispatch(key, this.__ce__, false); - var v = value; - value = undefined; - return v; - }, - 'has': function has(key) { - return dispatch(key, this.__ce__, false), dispatched; - }, - 'set': function set(key, value) { - dispatch(key, this.__ce__, true); - key.addEventListener(this.__ce__.type, new Handler(value), false); - return this; - }, - }; - - return DOMMap; - })(); - - function Dict() {} - Dict.prototype = (Object.create || Object)(null); - - // https://dom.spec.whatwg.org/#interface-eventtarget - - function createEventListener(type, callback, options) { - function eventListener(e) { - if (eventListener.once) { - e.currentTarget.removeEventListener(e.type, callback, eventListener); - eventListener.removed = true; - } - if (eventListener.passive) { - e.preventDefault = createEventListener.preventDefault; - } - if (typeof eventListener.callback === 'function') { - /* jshint validthis: true */ - eventListener.callback.call(this, e); - } else if (eventListener.callback) { - eventListener.callback.handleEvent(e); - } - if (eventListener.passive) { - delete e.preventDefault; - } - } - eventListener.type = type; - eventListener.callback = callback; - eventListener.capture = !!options.capture; - eventListener.passive = !!options.passive; - eventListener.once = !!options.once; - // currently pointless but specs say to use it, so ... - eventListener.removed = false; - return eventListener; - } - - createEventListener.preventDefault = function preventDefault() {}; - - var Event = global.CustomEvent, - dE = global.dispatchEvent, - aEL = global.addEventListener, - rEL = global.removeEventListener, - counter = 0, - increment = function () { - counter++; - }, - indexOf = - [].indexOf || - function indexOf(value) { - var length = this.length; - while (length--) { - if (this[length] === value) { - break; - } - } - return length; - }, - getListenerKey = function (options) { - return ''.concat(options.capture ? '1' : '0', options.passive ? '1' : '0', options.once ? '1' : '0'); - }, - augment; - - try { - aEL('_', increment, { once: true }); - dE(new Event('_')); - dE(new Event('_')); - rEL('_', increment, { once: true }); - } catch (o_O) {} - - if (counter !== 1) { - (function () { - var dm = new DOMMap(); - function createAEL(aEL) { - return function addEventListener(type, handler, options) { - if (options && typeof options !== 'boolean') { - var info = dm.get(this), - key = getListenerKey(options), - i, - tmp, - wrap; - if (!info) dm.set(this, (info = new Dict())); - if (!(type in info)) - info[type] = { - handler: [], - wrap: [], - }; - tmp = info[type]; - i = indexOf.call(tmp.handler, handler); - if (i < 0) { - i = tmp.handler.push(handler) - 1; - tmp.wrap[i] = wrap = new Dict(); - } else { - wrap = tmp.wrap[i]; - } - if (!(key in wrap)) { - wrap[key] = createEventListener(type, handler, options); - aEL.call(this, type, wrap[key], wrap[key].capture); - } - } else { - aEL.call(this, type, handler, options); - } - }; - } - function createREL(rEL) { - return function removeEventListener(type, handler, options) { - if (options && typeof options !== 'boolean') { - var info = dm.get(this), - key, - i, - tmp, - wrap; - if (info && type in info) { - tmp = info[type]; - i = indexOf.call(tmp.handler, handler); - if (-1 < i) { - key = getListenerKey(options); - wrap = tmp.wrap[i]; - if (key in wrap) { - rEL.call(this, type, wrap[key], wrap[key].capture); - delete wrap[key]; - // return if there are other wraps - for (key in wrap) return; - // otherwise remove all the things - tmp.handler.splice(i, 1); - tmp.wrap.splice(i, 1); - // if there are no other handlers - if (tmp.handler.length === 0) - // drop the info[type] entirely - delete info[type]; - } - } - } - } else { - rEL.call(this, type, handler, options); - } - }; - } - - augment = function (Constructor) { - if (!Constructor) return; - var proto = Constructor.prototype; - proto.addEventListener = createAEL(proto.addEventListener); - proto.removeEventListener = createREL(proto.removeEventListener); - }; - - if (global.EventTarget) { - augment(EventTarget); - } else { - augment(global.Text); - augment(global.Element || global.HTMLElement); - augment(global.HTMLDocument); - augment(global.Window || { prototype: global }); - augment(global.XMLHttpRequest); - } - })(); - } -})(window); diff --git a/tgui/packages/tgui-polyfill/03-css-om.js b/tgui/packages/tgui-polyfill/03-css-om.js deleted file mode 100644 index 534265e6f7..0000000000 --- a/tgui/packages/tgui-polyfill/03-css-om.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * CSS Object Model patches - * - * Adapted from: https://github.com/shawnbot/aight - * - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -/* eslint-disable */ -(function (Proto) { - 'use strict'; - - if (typeof Proto.setAttribute !== 'undefined') { - function toAttr(prop) { - return prop.replace(/-[a-z]/g, function (bit) { - return bit[1].toUpperCase(); - }); - } - - Proto.setProperty = function (prop, value) { - var attr = toAttr(prop); - if (!value) { - return this.removeAttribute(attr); - } - var str = String(value); - return this.setAttribute(attr, str); - }; - - Proto.getPropertyValue = function (prop) { - var attr = toAttr(prop); - return this.getAttribute(attr) || null; - }; - - Proto.removeProperty = function (prop) { - var attr = toAttr(prop); - var value = this.getAttribute(attr); - this.removeAttribute(attr); - return value; - }; - } -})(CSSStyleDeclaration.prototype); diff --git a/tgui/packages/tgui-polyfill/1-misc.js b/tgui/packages/tgui-polyfill/1-misc.js new file mode 100644 index 0000000000..cefbdbdbdf --- /dev/null +++ b/tgui/packages/tgui-polyfill/1-misc.js @@ -0,0 +1,48 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +/* eslint-disable */ +(function () { + 'use strict'; + + // ie11 polyfills + !(function () { + // append + function t() { + var e = Array.prototype.slice.call(arguments), + n = document.createDocumentFragment(); + e.forEach(function (e) { + var t = e instanceof Node; + n.appendChild(t ? e : document.createTextNode(String(e))); + }), + this.appendChild(n); + } + // remove + function n() { + this.parentNode && this.parentNode.removeChild(this); + } + + // add to prototype + [Element.prototype, Document.prototype, DocumentFragment.prototype].forEach( + function (e) { + e.hasOwnProperty('append') || + Object.defineProperty(e, 'append', { + configurable: !0, + enumerable: !0, + writable: !0, + value: t, + }); + e.hasOwnProperty('remove') || + Object.defineProperty(e, 'remove', { + configurable: !0, + enumerable: !0, + writable: !0, + value: n, + }); + } + ); + })(); +})(); diff --git a/tgui/packages/tgui-polyfill/10-misc.js b/tgui/packages/tgui-polyfill/10-misc.js deleted file mode 100644 index f2d69f288c..0000000000 --- a/tgui/packages/tgui-polyfill/10-misc.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -/* eslint-disable */ -(function () { - 'use strict'; - - // Necessary polyfill to make Webpack code splitting work on IE8 - if (!Function.prototype.bind) - (function () { - var slice = Array.prototype.slice; - Function.prototype.bind = function () { - var thatFunc = this, - thatArg = arguments[0]; - var args = slice.call(arguments, 1); - if (typeof thatFunc !== 'function') { - // closest thing possible to the ECMAScript 5 - // internal IsCallable function - throw new TypeError('Function.prototype.bind - ' + 'what is trying to be bound is not callable'); - } - return function () { - var funcArgs = args.concat(slice.call(arguments)); - return thatFunc.apply(thatArg, funcArgs); - }; - }; - })(); - - if (!Array.prototype['forEach']) { - Array.prototype.forEach = function (callback, thisArg) { - if (this == null) { - throw new TypeError('Array.prototype.forEach called on null or undefined'); - } - var T, k; - var O = Object(this); - var len = O.length >>> 0; - if (typeof callback !== 'function') { - throw new TypeError(callback + ' is not a function'); - } - if (arguments.length > 1) { - T = thisArg; - } - k = 0; - while (k < len) { - var kValue; - if (k in O) { - kValue = O[k]; - callback.call(T, kValue, k, O); - } - k++; - } - }; - } - - // Inferno needs Int32Array, and it is not covered by core-js. - if (!window.Int32Array) { - window.Int32Array = Array; - } -})(); diff --git a/tgui/packages/tgui-polyfill/package.json b/tgui/packages/tgui-polyfill/package.json index a2f060fcc5..8e43bb8722 100644 --- a/tgui/packages/tgui-polyfill/package.json +++ b/tgui/packages/tgui-polyfill/package.json @@ -1,16 +1,16 @@ { "private": true, "name": "tgui-polyfill", - "version": "4.3.0", + "version": "5.0.0", "scripts": { - "tgui-polyfill:build": "terser 00-html5shiv.js 01-ie8.js 02-dom4.js 03-css-om.js 10-misc.js --ie8 -f ascii_only,comments=false -o ../../public/tgui-polyfill.min.js" + "tgui-polyfill:build": "terser 1-misc.js -f ascii_only,comments=false -o ../../public/tgui-polyfill.min.js" }, "dependencies": { - "core-js": "^3.22.5", - "regenerator-runtime": "^0.13.9", - "unfetch": "^4.2.0" + "core-js": "^3.33.3", + "regenerator-runtime": "^0.14.0", + "unfetch": "^5.0.0" }, "devDependencies": { - "terser": "^5.14.2" + "terser": "^5.24.0" } } diff --git a/tgui/packages/tgui/assets.ts b/tgui/packages/tgui/assets.ts index e519d0c2f7..f62bf652de 100644 --- a/tgui/packages/tgui/assets.ts +++ b/tgui/packages/tgui/assets.ts @@ -4,10 +4,10 @@ * @license MIT */ -import { Action, AnyAction, Middleware } from '../common/redux'; - import { Dispatch } from 'common/redux'; +import { Action, AnyAction, Middleware } from '../common/redux'; + const EXCLUDED_PATTERNS = [/v4shim/i]; const loadedMappings: Record = {}; diff --git a/tgui/packages/tgui/backend.ts b/tgui/packages/tgui/backend.ts index 4a45529c65..4af2e7418c 100644 --- a/tgui/packages/tgui/backend.ts +++ b/tgui/packages/tgui/backend.ts @@ -13,6 +13,7 @@ import { perf } from 'common/perf'; import { createAction } from 'common/redux'; + import { setupDrag } from './drag'; import { globalEvents } from './events'; import { focusMap } from './focus'; @@ -228,7 +229,7 @@ export const backendMiddleware = (store) => { if (process.env.NODE_ENV !== 'production') { logger.log( 'visible in', - perf.measure('render/finish', 'resume/finish') + perf.measure('render/finish', 'resume/finish'), ); } }); @@ -324,10 +325,11 @@ type StateWithSetter = [T, (nextState: T) => void]; * @param context React context. * @param key Key which uniquely identifies this state in Redux store. * @param initialState Initializes your global variable with this value. + * @deprecated Use useState and useEffect when you can. Pass the state as a prop. */ export const useLocalState = ( key: string, - initialState: T + initialState: T, ): StateWithSetter => { const state = globalStore?.getState()?.backend; const sharedStates = state?.shared ?? {}; @@ -342,7 +344,7 @@ export const useLocalState = ( typeof nextState === 'function' ? nextState(sharedState) : nextState, - }) + }), ); }, ]; @@ -364,7 +366,7 @@ export const useLocalState = ( */ export const useSharedState = ( key: string, - initialState: T + initialState: T, ): StateWithSetter => { const state = globalStore?.getState()?.backend; const sharedStates = state?.shared ?? {}; @@ -377,9 +379,19 @@ export const useSharedState = ( key, value: JSON.stringify( - typeof nextState === 'function' ? nextState(sharedState) : nextState + typeof nextState === 'function' + ? nextState(sharedState) + : nextState, ) || '', }); }, ]; }; + +export const useDispatch = () => { + return globalStore.dispatch; +}; + +export const useSelector = (selector: (state: any) => any) => { + return selector(globalStore?.getState()); +}; diff --git a/tgui/packages/tgui/components/AnimatedNumber.tsx b/tgui/packages/tgui/components/AnimatedNumber.tsx index 53bdba90ab..435ed992d3 100644 --- a/tgui/packages/tgui/components/AnimatedNumber.tsx +++ b/tgui/packages/tgui/components/AnimatedNumber.tsx @@ -5,7 +5,7 @@ */ import { clamp, toFixed } from 'common/math'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; const isSafeNumber = (value: number) => { // prettier-ignore @@ -100,8 +100,6 @@ export class AnimatedNumber extends Component { this.startTicking(); } - // We render the inner `span` directly using a ref to bypass inferno diffing - // and reach 60 frames per second--tell inferno not to re-render this tree. return false; } @@ -157,7 +155,6 @@ export class AnimatedNumber extends Component { } if (this.ref.current) { - // Directly update the inner span, without bothering inferno. this.ref.current.textContent = this.getText(); } } diff --git a/tgui/packages/tgui/components/Autofocus.tsx b/tgui/packages/tgui/components/Autofocus.tsx index 28945dd7aa..a0b3f6f765 100644 --- a/tgui/packages/tgui/components/Autofocus.tsx +++ b/tgui/packages/tgui/components/Autofocus.tsx @@ -1,19 +1,17 @@ -import { Component, createRef } from 'inferno'; +import { createRef, PropsWithChildren, useEffect } from 'react'; -export class Autofocus extends Component { - ref = createRef(); +export const Autofocus = (props: PropsWithChildren) => { + const ref = createRef(); - componentDidMount() { + useEffect(() => { setTimeout(() => { - this.ref.current?.focus(); + ref.current?.focus(); }, 1); - } + }, []); - render() { - return ( -
- {this.props.children} -
- ); - } -} + return ( +
+ {props.children} +
+ ); +}; diff --git a/tgui/packages/tgui/components/Blink.jsx b/tgui/packages/tgui/components/Blink.jsx index bd781336b4..dc12f84346 100644 --- a/tgui/packages/tgui/components/Blink.jsx +++ b/tgui/packages/tgui/components/Blink.jsx @@ -1,11 +1,11 @@ -import { Component } from 'inferno'; +import { Component } from 'react'; const DEFAULT_BLINKING_INTERVAL = 1000; const DEFAULT_BLINKING_TIME = 1000; export class Blink extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.state = { hidden: false, }; @@ -55,13 +55,14 @@ export class Blink extends Component { clearTimeout(this.timer); } - render(props) { + render() { return ( - {props.children} + }} + > + {this.props.children} ); } diff --git a/tgui/packages/tgui/components/BlockQuote.jsx b/tgui/packages/tgui/components/BlockQuote.tsx similarity index 71% rename from tgui/packages/tgui/components/BlockQuote.jsx rename to tgui/packages/tgui/components/BlockQuote.tsx index 8c0f53a46a..3c627d3fac 100644 --- a/tgui/packages/tgui/components/BlockQuote.jsx +++ b/tgui/packages/tgui/components/BlockQuote.tsx @@ -5,9 +5,11 @@ */ import { classes } from 'common/react'; -import { Box } from './Box'; -export const BlockQuote = (props) => { +import { Box, BoxProps } from './Box'; + +export function BlockQuote(props: BoxProps) { const { className, ...rest } = props; + return ; -}; +} diff --git a/tgui/packages/tgui/components/BodyZoneSelector.tsx b/tgui/packages/tgui/components/BodyZoneSelector.tsx index cf8dc430e2..306b7002eb 100644 --- a/tgui/packages/tgui/components/BodyZoneSelector.tsx +++ b/tgui/packages/tgui/components/BodyZoneSelector.tsx @@ -1,6 +1,7 @@ -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; + import { resolveAsset } from '../assets'; -import { Box } from './Box'; +import { Image } from './Image'; export enum BodyZone { Head = 'head', @@ -84,9 +85,9 @@ export class BodyZoneSelector extends Component< width: `${32 * scale}px`, height: `${32 * scale}px`, position: 'relative', - }}> - + { const onClick = this.props.onClick; @@ -112,38 +113,33 @@ export class BodyZoneSelector extends Component< }); }} style={{ - '-ms-interpolation-mode': 'nearest-neighbor', - 'position': 'absolute', - 'width': `${32 * scale}px`, - 'height': `${32 * scale}px`, + position: 'absolute', + width: `${32 * scale}px`, + height: `${32 * scale}px`, }} /> {selectedZone && ( - )} {hoverZone && hoverZone !== selectedZone && ( - )} diff --git a/tgui/packages/tgui/components/Box.tsx b/tgui/packages/tgui/components/Box.tsx index 96244d5bd7..2ee476fddd 100644 --- a/tgui/packages/tgui/components/Box.tsx +++ b/tgui/packages/tgui/components/Box.tsx @@ -4,84 +4,65 @@ * @license MIT */ -import { BooleanLike, classes, pureComponentHooks } from 'common/react'; -import { createVNode, InfernoNode, SFC } from 'inferno'; -import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; -import { CSS_COLORS } from '../constants'; +import { BooleanLike, classes } from 'common/react'; +import { + createElement, + KeyboardEventHandler, + MouseEventHandler, + ReactNode, + UIEventHandler, +} from 'react'; -export type BoxProps = { - [key: string]: any; - as?: string; - className?: string | BooleanLike; - children?: InfernoNode; - position?: string | BooleanLike; - overflow?: string | BooleanLike; - overflowX?: string | BooleanLike; - overflowY?: string | BooleanLike; - top?: string | BooleanLike; - bottom?: string | BooleanLike; - left?: string | BooleanLike; - right?: string | BooleanLike; - width?: string | BooleanLike; - minWidth?: string | BooleanLike; - maxWidth?: string | BooleanLike; - height?: string | BooleanLike; - minHeight?: string | BooleanLike; - maxHeight?: string | BooleanLike; - fontSize?: string | BooleanLike; - fontFamily?: string; - lineHeight?: string | BooleanLike; - opacity?: number; - textAlign?: string | BooleanLike; - verticalAlign?: string | BooleanLike; - textTransform?: string | BooleanLike; // VOREStation Addition - inline?: BooleanLike; - bold?: BooleanLike; - italic?: BooleanLike; - nowrap?: BooleanLike; - preserveWhitespace?: BooleanLike; - m?: string | BooleanLike; - mx?: string | BooleanLike; - my?: string | BooleanLike; - mt?: string | BooleanLike; - mb?: string | BooleanLike; - ml?: string | BooleanLike; - mr?: string | BooleanLike; - p?: string | BooleanLike; - px?: string | BooleanLike; - py?: string | BooleanLike; - pt?: string | BooleanLike; - pb?: string | BooleanLike; - pl?: string | BooleanLike; - pr?: string | BooleanLike; - color?: string | BooleanLike; - textColor?: string | BooleanLike; - backgroundColor?: string | BooleanLike; - // VOREStation Addition Start - // Flex props - flexGrow?: string | BooleanLike; - flexWrap?: string | BooleanLike; - flexBasis?: string | BooleanLike; - flex?: string | BooleanLike; - // VOREStation Addition End - fillPositionedParent?: boolean; +import { CSS_COLORS } from '../constants'; +import { logger } from '../logging'; + +type BooleanProps = Partial>; +type StringProps = Partial< + Record +>; + +export type EventHandlers = Partial<{ + onClick: MouseEventHandler; + onContextMenu: MouseEventHandler; + onDoubleClick: MouseEventHandler; + onKeyDown: KeyboardEventHandler; + onKeyUp: KeyboardEventHandler; + onMouseDown: MouseEventHandler; + onMouseMove: MouseEventHandler; + onMouseOver: MouseEventHandler; + onMouseUp: MouseEventHandler; + onScroll: UIEventHandler; +}>; + +export type BoxProps = Partial<{ + as: string; + children: ReactNode; + className: string | BooleanLike; + style: Partial; +}> & + BooleanProps & + StringProps & + EventHandlers; + +// Don't you dare put this elsewhere +type DangerDoNotUse = { + dangerouslySetInnerHTML?: { + __html: any; + }; }; /** * Coverts our rem-like spacing unit into a CSS unit. */ -export const unit = (value: unknown): string | undefined => { +export const unit = (value: unknown) => { if (typeof value === 'string') { // Transparently convert pixels into rem units - if (value.endsWith('px') && !Byond.IS_LTE_IE8) { + if (value.endsWith('px')) { return parseFloat(value) / 12 + 'rem'; } return value; } if (typeof value === 'number') { - if (Byond.IS_LTE_IE8) { - return value * 12 + 'px'; - } return value + 'rem'; } }; @@ -89,7 +70,7 @@ export const unit = (value: unknown): string | undefined => { /** * Same as `unit`, but half the size for integers numbers. */ -export const halfUnit = (value: unknown): string | undefined => { +export const halfUnit = (value: unknown) => { if (typeof value === 'string') { return unit(value); } @@ -101,7 +82,7 @@ export const halfUnit = (value: unknown): string | undefined => { const isColorCode = (str: unknown) => !isColorClass(str); const isColorClass = (str: unknown): boolean => { - return typeof str === 'string' && CSS_COLORS.includes(str); + return typeof str === 'string' && CSS_COLORS.includes(str as any); }; const mapRawPropTo = (attrName) => (style, value) => { @@ -136,71 +117,69 @@ const mapColorPropTo = (attrName) => (style, value) => { } }; -const styleMapperByPropName = { - // Direct mapping - position: mapRawPropTo('position'), - overflow: mapRawPropTo('overflow'), - overflowX: mapRawPropTo('overflow-x'), - overflowY: mapRawPropTo('overflow-y'), - top: mapUnitPropTo('top', unit), +// String / number props +const stringStyleMap = { + align: mapRawPropTo('textAlign'), bottom: mapUnitPropTo('bottom', unit), - left: mapUnitPropTo('left', unit), - right: mapUnitPropTo('right', unit), - width: mapUnitPropTo('width', unit), - minWidth: mapUnitPropTo('min-width', unit), - maxWidth: mapUnitPropTo('max-width', unit), + colSpan: mapRawPropTo('colSpan'), + fontFamily: mapRawPropTo('fontFamily'), + fontSize: mapUnitPropTo('fontSize', unit), + fontWeight: mapRawPropTo('fontWeight'), height: mapUnitPropTo('height', unit), - minHeight: mapUnitPropTo('min-height', unit), - maxHeight: mapUnitPropTo('max-height', unit), - fontSize: mapUnitPropTo('font-size', unit), - fontFamily: mapRawPropTo('font-family'), + left: mapUnitPropTo('left', unit), + maxHeight: mapUnitPropTo('maxHeight', unit), + maxWidth: mapUnitPropTo('maxWidth', unit), + minHeight: mapUnitPropTo('minHeight', unit), + minWidth: mapUnitPropTo('minWidth', unit), + opacity: mapRawPropTo('opacity'), + overflow: mapRawPropTo('overflow'), + overflowX: mapRawPropTo('overflowX'), + overflowY: mapRawPropTo('overflowY'), + position: mapRawPropTo('position'), + right: mapUnitPropTo('right', unit), + textAlign: mapRawPropTo('textAlign'), + top: mapUnitPropTo('top', unit), + verticalAlign: mapRawPropTo('verticalAlign'), + width: mapUnitPropTo('width', unit), + lineHeight: (style, value) => { if (typeof value === 'number') { - style['line-height'] = value; + style['lineHeight'] = value; } else if (typeof value === 'string') { - style['line-height'] = unit(value); + style['lineHeight'] = unit(value); } }, - opacity: mapRawPropTo('opacity'), - textAlign: mapRawPropTo('text-align'), - verticalAlign: mapRawPropTo('vertical-align'), - textTransform: mapRawPropTo('text-transform'), // VOREStation Addition - // Boolean props - inline: mapBooleanPropTo('display', 'inline-block'), - bold: mapBooleanPropTo('font-weight', 'bold'), - italic: mapBooleanPropTo('font-style', 'italic'), - nowrap: mapBooleanPropTo('white-space', 'nowrap'), - preserveWhitespace: mapBooleanPropTo('white-space', 'pre-wrap'), - // Margins + // Margin m: mapDirectionalUnitPropTo('margin', halfUnit, [ - 'top', - 'bottom', - 'left', - 'right', + 'Top', + 'Bottom', + 'Left', + 'Right', ]), - mx: mapDirectionalUnitPropTo('margin', halfUnit, ['left', 'right']), - my: mapDirectionalUnitPropTo('margin', halfUnit, ['top', 'bottom']), - mt: mapUnitPropTo('margin-top', halfUnit), - mb: mapUnitPropTo('margin-bottom', halfUnit), - ml: mapUnitPropTo('margin-left', halfUnit), - mr: mapUnitPropTo('margin-right', halfUnit), - // Margins + mb: mapUnitPropTo('marginBottom', halfUnit), + ml: mapUnitPropTo('marginLeft', halfUnit), + mr: mapUnitPropTo('marginRight', halfUnit), + mt: mapUnitPropTo('marginTop', halfUnit), + mx: mapDirectionalUnitPropTo('margin', halfUnit, ['Left', 'Right']), + my: mapDirectionalUnitPropTo('margin', halfUnit, ['Top', 'Bottom']), + // Padding p: mapDirectionalUnitPropTo('padding', halfUnit, [ - 'top', - 'bottom', - 'left', - 'right', + 'Top', + 'Bottom', + 'Left', + 'Right', ]), - px: mapDirectionalUnitPropTo('padding', halfUnit, ['left', 'right']), - py: mapDirectionalUnitPropTo('padding', halfUnit, ['top', 'bottom']), - pt: mapUnitPropTo('padding-top', halfUnit), - pb: mapUnitPropTo('padding-bottom', halfUnit), - pl: mapUnitPropTo('padding-left', halfUnit), - pr: mapUnitPropTo('padding-right', halfUnit), + pb: mapUnitPropTo('paddingBottom', halfUnit), + pl: mapUnitPropTo('paddingLeft', halfUnit), + pr: mapUnitPropTo('paddingRight', halfUnit), + pt: mapUnitPropTo('paddingTop', halfUnit), + px: mapDirectionalUnitPropTo('padding', halfUnit, ['Left', 'Right']), + py: mapDirectionalUnitPropTo('padding', halfUnit, ['Top', 'Bottom']), // Color props color: mapColorPropTo('color'), textColor: mapColorPropTo('color'), - backgroundColor: mapColorPropTo('background-color'), + backgroundColor: mapColorPropTo('backgroundColor'), + // VOREStation Addition Start // Flex props flexGrow: mapRawPropTo('flex-grow'), @@ -208,7 +187,11 @@ const styleMapperByPropName = { flexBasis: mapRawPropTo('flex-basis'), flex: mapRawPropTo('flex'), // VOREStation Addition End - // Utility props +} as const; + +// Boolean props +const booleanStyleMap = { + bold: mapBooleanPropTo('fontWeight', 'bold'), fillPositionedParent: (style, value) => { if (value) { style['position'] = 'absolute'; @@ -218,44 +201,37 @@ const styleMapperByPropName = { style['right'] = 0; } }, -}; + inline: mapBooleanPropTo('display', 'inline-block'), + italic: mapBooleanPropTo('fontStyle', 'italic'), + nowrap: mapBooleanPropTo('whiteSpace', 'nowrap'), + preserveWhitespace: mapBooleanPropTo('whiteSpace', 'pre-wrap'), +} as const; + +export const computeBoxProps = (props) => { + const computedProps: Record = {}; + const computedStyles: Record = {}; -export const computeBoxProps = (props: BoxProps) => { - const computedProps: HTMLAttributes = {}; - const computedStyles = {}; // Compute props for (let propName of Object.keys(props)) { if (propName === 'style') { continue; } - // IE8: onclick workaround - if (Byond.IS_LTE_IE8 && propName === 'onClick') { - computedProps.onclick = props[propName]; - continue; - } + const propValue = props[propName]; - const mapPropToStyle = styleMapperByPropName[propName]; + + const mapPropToStyle = + stringStyleMap[propName] || booleanStyleMap[propName]; + if (mapPropToStyle) { mapPropToStyle(computedStyles, propValue); } else { computedProps[propName] = propValue; } } - // Concatenate styles - let style = ''; - for (let attrName of Object.keys(computedStyles)) { - const attrValue = computedStyles[attrName]; - style += attrName + ':' + attrValue + ';'; - } - if (props.style) { - for (let attrName of Object.keys(props.style)) { - const attrValue = props.style[attrName]; - style += attrName + ':' + attrValue + ';'; - } - } - if (style.length > 0) { - computedProps.style = style; - } + + // Merge computed styles and any directly provided styles + computedProps.style = { ...computedStyles, ...props.style }; + return computedProps; }; @@ -268,27 +244,28 @@ export const computeBoxClassName = (props: BoxProps) => { ]); }; -export const Box: SFC = (props: BoxProps) => { +export const Box = (props: BoxProps & DangerDoNotUse) => { const { as = 'div', className, children, ...rest } = props; - // Render props - if (typeof children === 'function') { - return children(computeBoxProps(props)); - } - const computedClassName = - typeof className === 'string' - ? className + ' ' + computeBoxClassName(rest) - : computeBoxClassName(rest); + + // Compute class name and styles + const computedClassName = className + ? `${className} ${computeBoxClassName(rest)}` + : computeBoxClassName(rest); const computedProps = computeBoxProps(rest); - // Render a wrapper element - return createVNode( - VNodeFlags.HtmlElement, - as, - computedClassName, + + if (as === 'img') { + logger.error( + 'Box component cannot be used as an image. Use Image component instead.', + ); + } + + // Render the component + return createElement( + typeof as === 'string' ? as : 'div', + { + ...computedProps, + className: computedClassName, + }, children, - ChildFlags.UnknownChildren, - computedProps, - undefined ); }; - -Box.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui/components/Button.jsx b/tgui/packages/tgui/components/Button.jsx index 58d879d228..33f00c7311 100644 --- a/tgui/packages/tgui/components/Button.jsx +++ b/tgui/packages/tgui/components/Button.jsx @@ -5,15 +5,13 @@ */ import { KEY_ENTER, KEY_ESCAPE, KEY_SPACE } from 'common/keycodes'; -import { classes, pureComponentHooks } from 'common/react'; -import { Component, createRef } from 'inferno'; -import { createLogger } from '../logging'; +import { classes } from 'common/react'; +import { Component, createRef } from 'react'; + import { Box, computeBoxClassName, computeBoxProps } from './Box'; import { Icon } from './Icon'; import { Tooltip } from './Tooltip'; -const logger = createLogger('Button'); - export const Button = (props) => { const { className, @@ -34,30 +32,18 @@ export const Button = (props) => { circular, content, children, - onclick, onClick, verticalAlignContent, ...rest } = props; const hasContent = !!(content || children); - // A warning about the lowercase onclick - if (onclick) { - logger.warn( - `Lowercase 'onclick' is not supported on Button and lowercase` + - ` prop names are discouraged in general. Please use a camelCase` + - `'onClick' instead and read: ` + - `https://infernojs.org/docs/guides/event-handling` - ); - } + rest.onClick = (e) => { if (!disabled && onClick) { onClick(e); } }; - // IE8: Use "unselectable" because "user-select" doesn't work. - if (Byond.IS_LTE_IE8) { - rest.unselectable = true; - } + let buttonContent = (
{ return; } }} - {...computeBoxProps(rest)}> + {...computeBoxProps(rest)} + >
{icon && iconPosition !== 'right' && ( { return buttonContent; }; -Button.defaultHooks = pureComponentHooks; - export const ButtonCheckbox = (props) => { const { checked, ...rest } = props; return ( @@ -153,8 +138,8 @@ export const ButtonCheckbox = (props) => { Button.Checkbox = ButtonCheckbox; export class ButtonConfirm extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.state = { clickedOnce: false, }; @@ -204,8 +189,8 @@ export class ButtonConfirm extends Component { Button.Confirm = ButtonConfirm; export class ButtonInput extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.inputRef = createRef(); this.state = { inInput: false, @@ -267,15 +252,16 @@ export class ButtonInput extends Component { 'Button--color--' + color, ])} {...rest} - onClick={() => this.setInInput(true)}> + onClick={() => this.setInInput(true)} + > {icon && }
{content}
{ if (!this.state.inInput) { @@ -313,8 +299,8 @@ export class ButtonInput extends Component { Button.Input = ButtonInput; export class ButtonFile extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.inputRef = createRef(); } diff --git a/tgui/packages/tgui/components/ByondUi.jsx b/tgui/packages/tgui/components/ByondUi.jsx index 4623fe5770..c119879dd2 100644 --- a/tgui/packages/tgui/components/ByondUi.jsx +++ b/tgui/packages/tgui/components/ByondUi.jsx @@ -6,7 +6,8 @@ import { shallowDiffers } from 'common/react'; import { debounce } from 'common/timer'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; + import { createLogger } from '../logging'; import { computeBoxProps } from './Box'; @@ -92,20 +93,12 @@ export class ByondUi extends Component { } componentDidMount() { - // IE8: It probably works, but fuck you anyway. - if (Byond.IS_LTE_IE10) { - return; - } window.addEventListener('resize', this.handleResize); this.componentDidUpdate(); this.handleResize(); } componentDidUpdate() { - // IE8: It probably works, but fuck you anyway. - if (Byond.IS_LTE_IE10) { - return; - } const { params = {} } = this.props; const box = getBoundingBox(this.containerRef.current); logger.debug('bounding box', box); @@ -118,10 +111,6 @@ export class ByondUi extends Component { } componentWillUnmount() { - // IE8: It probably works, but fuck you anyway. - if (Byond.IS_LTE_IE10) { - return; - } window.removeEventListener('resize', this.handleResize); this.byondUiElement.unmount(); } @@ -131,7 +120,7 @@ export class ByondUi extends Component { return (
{/* Filler */} -
+
); } diff --git a/tgui/packages/tgui/components/Chart.jsx b/tgui/packages/tgui/components/Chart.tsx similarity index 53% rename from tgui/packages/tgui/components/Chart.jsx rename to tgui/packages/tgui/components/Chart.tsx index fac444bd1d..205dc6fbec 100644 --- a/tgui/packages/tgui/components/Chart.jsx +++ b/tgui/packages/tgui/components/Chart.tsx @@ -5,29 +5,57 @@ */ import { map, zipWith } from 'common/collections'; -import { pureComponentHooks } from 'common/react'; -import { Component, createRef } from 'inferno'; -import { Box } from './Box'; +import { Component, createRef, RefObject } from 'react'; -const normalizeData = (data, scale, rangeX, rangeY) => { +import { Box, BoxProps } from './Box'; + +type Props = { + data: number[][]; +} & Partial<{ + fillColor: string; + rangeX: [number, number]; + rangeY: [number, number]; + strokeColor: string; + strokeWidth: number; +}> & + BoxProps; + +type State = { + viewBox: [number, number]; +}; + +type Point = number[]; +type Range = [number, number]; + +const normalizeData = ( + data: Point[], + scale: number[], + rangeX?: Range, + rangeY?: Range, +) => { if (data.length === 0) { return []; } + const min = zipWith(Math.min)(...data); const max = zipWith(Math.max)(...data); + if (rangeX !== undefined) { min[0] = rangeX[0]; max[0] = rangeX[1]; } + if (rangeY !== undefined) { min[1] = rangeY[0]; max[1] = rangeY[1]; } - const normalized = map((point) => { - return zipWith((value, min, max, scale) => { + + const normalized = map((point: Point) => { + return zipWith((value: number, min: number, max: number, scale: number) => { return ((value - min) / (max - min)) * scale; })(point, min, max, scale); })(data); + return normalized; }; @@ -40,20 +68,18 @@ const dataToPolylinePoints = (data) => { return points; }; -class LineChart extends Component { - constructor(props) { +class LineChart extends Component { + ref: RefObject; + state: State; + + constructor(props: Props) { super(props); this.ref = createRef(); this.state = { // Initial guess viewBox: [600, 200], }; - this.handleResize = () => { - const element = this.ref.current; - this.setState({ - viewBox: [element.offsetWidth, element.offsetHeight], - }); - }; + this.handleResize = this.handleResize.bind(this); } componentDidMount() { @@ -65,6 +91,16 @@ class LineChart extends Component { window.removeEventListener('resize', this.handleResize); } + handleResize = () => { + const element = this.ref.current; + if (!element) { + return; + } + this.setState({ + viewBox: [element.offsetWidth, element.offsetHeight], + }); + }; + render() { const { data = [], @@ -87,41 +123,37 @@ class LineChart extends Component { normalized.push([-strokeWidth, first[1]]); } const points = dataToPolylinePoints(normalized); + const divProps = { ...rest, className: '', ref: this.ref }; + return ( - {(props) => ( -
- - - -
- )} + + + + +
); } } -LineChart.defaultHooks = pureComponentHooks; - -const Stub = (props) => null; - -// IE8: No inline svg support export const Chart = { - Line: Byond.IS_LTE_IE8 ? Stub : LineChart, + Line: LineChart, }; diff --git a/tgui/packages/tgui/components/Collapsible.jsx b/tgui/packages/tgui/components/Collapsible.jsx deleted file mode 100644 index f91eeddb45..0000000000 --- a/tgui/packages/tgui/components/Collapsible.jsx +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { Component } from 'inferno'; -import { Box } from './Box'; -import { Button } from './Button'; - -export class Collapsible extends Component { - constructor(props) { - super(props); - const { open } = props; - this.state = { - open: open || false, - }; - } - - render() { - const { props } = this; - const { open } = this.state; - const { children, color = 'default', title, buttons, ...rest } = props; - return ( - -
-
- -
- {buttons && ( -
{buttons}
- )} -
- {open && {children}} -
- ); - } -} diff --git a/tgui/packages/tgui/components/Collapsible.tsx b/tgui/packages/tgui/components/Collapsible.tsx new file mode 100644 index 0000000000..b470ed5ce6 --- /dev/null +++ b/tgui/packages/tgui/components/Collapsible.tsx @@ -0,0 +1,44 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +import { ReactNode, useState } from 'react'; + +import { Box, BoxProps } from './Box'; +import { Button } from './Button'; + +type Props = Partial<{ + buttons: ReactNode; + open: boolean; + title: ReactNode; +}> & + BoxProps; + +export function Collapsible(props: Props) { + const { children, color, title, buttons, ...rest } = props; + const [open, setOpen] = useState(props.open); + + return ( + +
+
+ +
+ {buttons && ( +
{buttons}
+ )} +
+ {open && {children}} +
+ ); +} diff --git a/tgui/packages/tgui/components/ColorBox.jsx b/tgui/packages/tgui/components/ColorBox.jsx deleted file mode 100644 index a6203ca469..0000000000 --- a/tgui/packages/tgui/components/ColorBox.jsx +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { classes, pureComponentHooks } from 'common/react'; -import { computeBoxClassName, computeBoxProps } from './Box'; - -export const ColorBox = (props) => { - // prettier-ignore - const { - content, - children, - className, - color, - backgroundColor, - ...rest - } = props; - rest.color = content ? null : 'transparent'; - rest.backgroundColor = color || backgroundColor; - return ( -
- {content || '.'} -
- ); -}; - -ColorBox.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui/components/ColorBox.tsx b/tgui/packages/tgui/components/ColorBox.tsx new file mode 100644 index 0000000000..b04cdd9241 --- /dev/null +++ b/tgui/packages/tgui/components/ColorBox.tsx @@ -0,0 +1,30 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +import { classes } from 'common/react'; +import { ReactNode } from 'react'; + +import { BoxProps, computeBoxClassName, computeBoxProps } from './Box'; + +type Props = { + content?: ReactNode; +} & BoxProps; + +export function ColorBox(props: Props) { + const { content, children, className, ...rest } = props; + + rest.color = content ? null : 'default'; + rest.backgroundColor = props.color || 'default'; + + return ( +
+ {content || '.'} +
+ ); +} diff --git a/tgui/packages/tgui/components/Dialog.tsx b/tgui/packages/tgui/components/Dialog.tsx index 277a03b284..897ce236a4 100644 --- a/tgui/packages/tgui/components/Dialog.tsx +++ b/tgui/packages/tgui/components/Dialog.tsx @@ -52,7 +52,8 @@ const DialogButton = (props: DialogButtonProps) => { ); diff --git a/tgui/packages/tgui/components/Dimmer.jsx b/tgui/packages/tgui/components/Dimmer.tsx similarity index 61% rename from tgui/packages/tgui/components/Dimmer.jsx rename to tgui/packages/tgui/components/Dimmer.tsx index 85e046ca0d..d1ddb64c4e 100644 --- a/tgui/packages/tgui/components/Dimmer.jsx +++ b/tgui/packages/tgui/components/Dimmer.tsx @@ -5,13 +5,15 @@ */ import { classes } from 'common/react'; -import { Box } from './Box'; -export const Dimmer = (props) => { +import { Box, BoxProps } from './Box'; + +export function Dimmer(props: BoxProps) { const { className, children, ...rest } = props; + return ( - +
{children}
); -}; +} diff --git a/tgui/packages/tgui/components/Divider.jsx b/tgui/packages/tgui/components/Divider.tsx similarity index 66% rename from tgui/packages/tgui/components/Divider.jsx rename to tgui/packages/tgui/components/Divider.tsx index 8cbf9b77d7..1582aeadfd 100644 --- a/tgui/packages/tgui/components/Divider.jsx +++ b/tgui/packages/tgui/components/Divider.tsx @@ -6,8 +6,14 @@ import { classes } from 'common/react'; -export const Divider = (props) => { - const { vertical, hidden } = props; +type Props = Partial<{ + hidden: boolean; + vertical: boolean; +}>; + +export function Divider(props: Props) { + const { hidden, vertical } = props; + return (
{ ])} /> ); -}; +} diff --git a/tgui/packages/tgui/components/DraggableControl.jsx b/tgui/packages/tgui/components/DraggableControl.jsx index 8ada6f2fa4..bb55287b02 100644 --- a/tgui/packages/tgui/components/DraggableControl.jsx +++ b/tgui/packages/tgui/components/DraggableControl.jsx @@ -5,8 +5,8 @@ */ import { clamp } from 'common/math'; -import { pureComponentHooks } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; + import { AnimatedNumber } from './AnimatedNumber'; const DEFAULT_UPDATE_RATE = 400; @@ -97,13 +97,13 @@ export class DraggableControl extends Component { state.internalValue = clamp( state.internalValue + (offset * step) / stepPixelSize, minValue - step, - maxValue + step + maxValue + step, ); // Clamp the final value state.value = clamp( state.internalValue - (state.internalValue % step) + stepOffset, minValue, - maxValue + maxValue, ); state.origin = getScalarScreenOffset(e, dragMatrix); } else if (Math.abs(offset) > 4) { @@ -196,8 +196,8 @@ export class DraggableControl extends Component { style={{ display: !editing ? 'none' : undefined, height: height, - 'line-height': lineHeight, - 'font-size': fontSize, + lineHeight: lineHeight, + fontsize: fontSize, }} onBlur={(e) => { if (!editing) { @@ -276,7 +276,6 @@ export class DraggableControl extends Component { } } -DraggableControl.defaultHooks = pureComponentHooks; DraggableControl.defaultProps = { minValue: -Infinity, maxValue: +Infinity, diff --git a/tgui/packages/tgui/components/Dropdown.tsx b/tgui/packages/tgui/components/Dropdown.tsx index 7fa0e3ae8d..4fabfbf0bb 100644 --- a/tgui/packages/tgui/components/Dropdown.tsx +++ b/tgui/packages/tgui/components/Dropdown.tsx @@ -1,386 +1,220 @@ -import { createPopper, VirtualElement } from '@popperjs/core'; import { classes } from 'common/react'; -import { Component, findDOMfromVNode, InfernoNode, render } from 'inferno'; -import { Box, BoxProps } from './Box'; +import { ReactNode, useCallback, useEffect, useRef, useState } from 'react'; + +import { BoxProps, unit } from './Box'; import { Button } from './Button'; import { Icon } from './Icon'; -import { Stack } from './Stack'; +import { Popper } from './Popper'; -export interface DropdownEntry { - displayText: string | number | InfernoNode; - value: string | number | Enumerator; +type DropdownEntry = { + displayText: ReactNode; + value: string | number; +}; + +type DropdownOption = string | DropdownEntry; + +type Props = { + /** An array of strings which will be displayed in the + dropdown when open. See Dropdown.tsx for more advanced usage with DropdownEntry */ + options: DropdownOption[]; + /** Called when a value is picked from the list, `value` is the value that was picked */ + onSelected: (value: any) => void; +} & Partial<{ + /** Whether to display previous / next buttons */ + buttons: boolean; + /** Whether to clip the selected text */ + clipSelectedText: boolean; + /** Color of dropdown button */ + color: string; + /** Disables the dropdown */ + disabled: boolean; + /** Text to always display in place of the selected text */ + displayText: ReactNode; + /** Icon to display in dropdown button */ + icon: string; + /** Angle of the icon */ + iconRotation: number; + /** Whether or not the icon should spin */ + iconSpin: boolean; + /** Width of the dropdown menu. Default: 15rem */ + menuWidth: string; + /** Whether or not the arrow on the right hand side of the dropdown button is visible */ + noChevron: boolean; + /** Called when dropdown button is clicked */ + onClick: (event) => void; + /** Dropdown renders over instead of below */ + over: boolean; + /** Currently selected entry */ + selected: string | number; +}> & + BoxProps; + +function getOptionValue(option: DropdownOption) { + return typeof option === 'string' ? option : option.value; } -type DropdownUniqueProps = { - options: string[] | DropdownEntry[]; - icon?: string; - iconRotation?: number; - clipSelectedText?: boolean; - width?: string; - menuWidth?: string; - over?: boolean; - color?: string; - nochevron?: boolean; - displayText?: string | number | InfernoNode; - onClick?: (event) => void; - // you freaks really are just doing anything with this shit - selected?: any; - onSelected?: (selected: any) => void; - buttons?: boolean; -}; +export function Dropdown(props: Props) { + const { + buttons, + className, + clipSelectedText = true, + color = 'default', + disabled, + displayText, + icon, + iconRotation, + iconSpin, + menuWidth = '15rem', + noChevron, + onClick, + onSelected, + options = [], + over, + selected, + width, + } = props; -export type DropdownProps = BoxProps & DropdownUniqueProps; + const [open, setOpen] = useState(false); + const adjustedOpen = over ? !open : open; + const innerRef = useRef(null); -const DEFAULT_OPTIONS = { - placement: 'left-start', - modifiers: [ - { - name: 'eventListeners', - enabled: false, - }, - ], -}; -const NULL_RECT: DOMRect = { - width: 0, - height: 0, - top: 0, - right: 0, - bottom: 0, - left: 0, - x: 0, - y: 0, - toJSON: () => null, -} as const; - -type DropdownState = { - selected?: string; - open: boolean; -}; - -const DROPDOWN_DEFAULT_CLASSNAMES = 'Layout Dropdown__menu'; -const DROPDOWN_SCROLL_CLASSNAMES = 'Layout Dropdown__menu-scroll'; - -export class Dropdown extends Component { - static renderedMenu: HTMLDivElement | undefined; - static singletonPopper: ReturnType | undefined; - static currentOpenMenu: Element | undefined; - static virtualElement: VirtualElement = { - getBoundingClientRect: () => - Dropdown.currentOpenMenu?.getBoundingClientRect() ?? NULL_RECT, - }; - menuContents: any; - state: DropdownState = { - open: false, - selected: this.props.selected, - }; - - handleClick = () => { - if (this.state.open) { - this.setOpen(false); - } - }; - - getDOMNode() { - return findDOMfromVNode(this.$LI, true); - } - - componentDidMount() { - const domNode = this.getDOMNode(); - - if (!domNode) { - return; - } - } - - openMenu() { - let renderedMenu = Dropdown.renderedMenu; - if (renderedMenu === undefined) { - renderedMenu = document.createElement('div'); - renderedMenu.className = DROPDOWN_DEFAULT_CLASSNAMES; - document.body.appendChild(renderedMenu); - Dropdown.renderedMenu = renderedMenu; - } - - const domNode = this.getDOMNode()!; - Dropdown.currentOpenMenu = domNode; - - renderedMenu.scrollTop = 0; - renderedMenu.style.width = - this.props.menuWidth || - // Hack, but domNode should *always* be the parent control meaning it will have width - // @ts-ignore - `${domNode.offsetWidth}px`; - renderedMenu.style.opacity = '1'; - renderedMenu.style.pointerEvents = 'auto'; - - // ie hack - // ie has this bizarre behavior where focus just silently fails if the - // element being targeted "isn't ready" - // 400 is probably way too high, but the lack of hotloading is testing my - // patience on tuning it - // I'm beyond giving a shit at this point it fucking works whatever - setTimeout(() => { - Dropdown.renderedMenu?.focus(); - }, 400); - this.renderMenuContent(); - } - - closeMenu() { - if (Dropdown.currentOpenMenu !== this.getDOMNode()) { - return; - } - - Dropdown.currentOpenMenu = undefined; - Dropdown.renderedMenu!.style.opacity = '0'; - Dropdown.renderedMenu!.style.pointerEvents = 'none'; - } - - componentWillUnmount() { - this.closeMenu(); - this.setOpen(false); - } - - renderMenuContent() { - const renderedMenu = Dropdown.renderedMenu; - if (!renderedMenu) { - return; - } - if (renderedMenu.offsetHeight > 200) { - renderedMenu.className = DROPDOWN_SCROLL_CLASSNAMES; - } else { - renderedMenu.className = DROPDOWN_DEFAULT_CLASSNAMES; - } - - const { options = [] } = this.props; - const ops = options.map((option) => { - let value, displayText; - - if (typeof option === 'string') { - displayText = option; - value = option; - } else if (option !== null) { - displayText = option.displayText; - value = option.value; + /** Update the selected value when clicking the left/right buttons */ + const updateSelected = useCallback( + (direction: 'previous' | 'next') => { + if (options.length < 1 || disabled) { + return; } + const startIndex = 0; + const endIndex = options.length - 1; - return ( -
{ - this.setSelected(value); - }}> - {displayText} -
+ let selectedIndex = options.findIndex( + (option) => getOptionValue(option) === selected, ); - }); - const to_render = ops.length ? ops : 'No Options Found'; - - render(
{to_render}
, renderedMenu, () => { - let singletonPopper = Dropdown.singletonPopper; - if (singletonPopper === undefined) { - singletonPopper = createPopper(Dropdown.virtualElement, renderedMenu!, { - ...DEFAULT_OPTIONS, - placement: 'bottom-start', - }); - - Dropdown.singletonPopper = singletonPopper; - } else { - singletonPopper.setOptions({ - ...DEFAULT_OPTIONS, - placement: 'bottom-start', - }); - - singletonPopper.update(); + if (selectedIndex < 0) { + selectedIndex = direction === 'next' ? endIndex : startIndex; } - }); - } - setOpen(open: boolean) { - this.setState((state) => ({ - ...state, - open, - })); - if (open) { - setTimeout(() => { - this.openMenu(); - window.addEventListener('click', this.handleClick); - }); - } else { - this.closeMenu(); - window.removeEventListener('click', this.handleClick); - } - } + let newIndex = selectedIndex; + if (direction === 'next') { + newIndex = selectedIndex === endIndex ? startIndex : selectedIndex++; + } else { + newIndex = selectedIndex === startIndex ? endIndex : selectedIndex--; + } - setSelected(selected: string) { - this.setState((state) => ({ - ...state, - selected, - })); - this.setOpen(false); - if (this.props.onSelected) { - this.props.onSelected(selected); - } - } + onSelected?.(getOptionValue(options[newIndex])); + }, + [disabled, onSelected, options, selected], + ); - getOptionValue(option): string { - return typeof option === 'string' ? option : option.value; - } + /** Allows the menu to be scrollable on open */ + useEffect(() => { + if (!open) return; - getSelectedIndex(): number { - const selected = this.state.selected || this.props.selected; - const { options = [] } = this.props; + innerRef.current?.focus(); + }, [open]); - return options.findIndex((option) => { - return this.getOptionValue(option) === selected; - }); - } + return ( + setOpen(false)} + placement={over ? 'top-start' : 'bottom-start'} + content={ +
+ {options.length === 0 && ( +
No options
+ )} - toPrevious(): void { - if (this.props.options.length < 1) { - return; - } + {options.map((option, index) => { + const value = getOptionValue(option); - let selectedIndex = this.getSelectedIndex(); - const startIndex = 0; - const endIndex = this.props.options.length - 1; - - const hasSelected = selectedIndex >= 0; - if (!hasSelected) { - selectedIndex = startIndex; - } - - const previousIndex = - selectedIndex === startIndex ? endIndex : selectedIndex - 1; - - this.setSelected(this.getOptionValue(this.props.options[previousIndex])); - } - - toNext(): void { - if (this.props.options.length < 1) { - return; - } - - let selectedIndex = this.getSelectedIndex(); - const startIndex = 0; - const endIndex = this.props.options.length - 1; - - const hasSelected = selectedIndex >= 0; - if (!hasSelected) { - selectedIndex = endIndex; - } - - const nextIndex = - selectedIndex === endIndex ? startIndex : selectedIndex + 1; - - this.setSelected(this.getOptionValue(this.props.options[nextIndex])); - } - - render() { - const { props } = this; - const { - icon, - iconRotation, - iconSpin, - clipSelectedText = true, - color = 'default', - dropdownStyle, - over, - nochevron, - width, - onClick, - onSelected, - selected, - disabled, - displayText, - buttons, - ...boxProps - } = props; - const { className, ...rest } = boxProps; - - const adjustedOpen = over ? !this.state.open : this.state.open; - - return ( - - - { + setOpen(false); + onSelected?.(value); + }} + > + {typeof option === 'string' ? option : option.displayText} +
+ ); + })} +
+ } + > +
+
+
{ - if (disabled && !this.state.open) { + if (disabled && !open) { return; } - this.setOpen(!this.state.open); - if (onClick) { - onClick(event); - } + setOpen(!open); + onClick?.(event); }} - {...rest}> + > {icon && ( )} - {displayText || this.state.selected} + }} + > + {displayText || selected} - {nochevron || ( + {!noChevron && ( )} - - - {buttons && ( - <> - +
+ {buttons && ( + <>
+
+ + ); } diff --git a/tgui/packages/tgui/components/FakeTerminal.jsx b/tgui/packages/tgui/components/FakeTerminal.jsx index d6479a2579..d3b7fbe65d 100644 --- a/tgui/packages/tgui/components/FakeTerminal.jsx +++ b/tgui/packages/tgui/components/FakeTerminal.jsx @@ -1,5 +1,6 @@ +import { Component, Fragment } from 'react'; + import { Box } from './Box'; -import { Component, Fragment } from 'inferno'; export class FakeTerminal extends Component { constructor(props) { diff --git a/tgui/packages/tgui/components/FitText.tsx b/tgui/packages/tgui/components/FitText.tsx index 334eb97073..049c887ed7 100644 --- a/tgui/packages/tgui/components/FitText.tsx +++ b/tgui/packages/tgui/components/FitText.tsx @@ -1,4 +1,10 @@ -import { Component, createRef, RefObject } from 'inferno'; +import { + Component, + createRef, + HTMLAttributes, + PropsWithChildren, + RefObject, +} from 'react'; const DEFAULT_ACCEPTABLE_DIFFERENCE = 5; @@ -7,7 +13,7 @@ type Props = { maxWidth: number; maxFontSize: number; native?: HTMLAttributes; -}; +} & PropsWithChildren; type State = { fontSize: number; @@ -19,8 +25,8 @@ export class FitText extends Component { fontSize: 0, }; - constructor() { - super(); + constructor(props: Props) { + super(props); this.resize = this.resize.bind(this); @@ -80,10 +86,12 @@ export class FitText extends Component { + fontSize: `${this.state.fontSize}px`, + ...(typeof this.props.native?.style === 'object' + ? this.props.native.style + : {}), + }} + > {this.props.children} ); diff --git a/tgui/packages/tgui/components/Flex.tsx b/tgui/packages/tgui/components/Flex.tsx index f67738280b..50dba27795 100644 --- a/tgui/packages/tgui/components/Flex.tsx +++ b/tgui/packages/tgui/components/Flex.tsx @@ -4,16 +4,20 @@ * @license MIT */ -import { BooleanLike, classes, pureComponentHooks } from 'common/react'; +import { classes } from 'common/react'; + import { BoxProps, computeBoxClassName, computeBoxProps, unit } from './Box'; -export type FlexProps = BoxProps & { - direction?: string | BooleanLike; - wrap?: string | BooleanLike; - align?: string | BooleanLike; - justify?: string | BooleanLike; - inline?: BooleanLike; -}; +export type FlexProps = Partial<{ + align: string | boolean; + direction: string; + inline: boolean; + justify: string; + scrollable: boolean; + style: Partial; + wrap: string | boolean; +}> & + BoxProps; export const computeFlexClassName = (props: FlexProps) => { return classes([ @@ -27,13 +31,14 @@ export const computeFlexClassName = (props: FlexProps) => { export const computeFlexProps = (props: FlexProps) => { const { className, direction, wrap, align, justify, inline, ...rest } = props; + return computeBoxProps({ style: { ...rest.style, - 'flex-direction': direction, - 'flex-wrap': wrap === true ? 'wrap' : wrap, - 'align-items': align, - 'justify-content': justify, + flexDirection: direction, + flexWrap: wrap === true ? 'wrap' : wrap, + alignItems: align, + justifyContent: justify, }, ...rest, }); @@ -49,15 +54,15 @@ export const Flex = (props) => { ); }; -Flex.defaultHooks = pureComponentHooks; - -export type FlexItemProps = BoxProps & { - grow?: number; - order?: number; - shrink?: number; - basis?: string | BooleanLike; - align?: string | BooleanLike; -}; +export type FlexItemProps = BoxProps & + Partial<{ + grow: number | boolean; + order: number; + shrink: number | boolean; + basis: string | number; + align: string | boolean; + style: Partial; + }>; export const computeFlexItemClassName = (props: FlexItemProps) => { return classes([ @@ -68,33 +73,26 @@ export const computeFlexItemClassName = (props: FlexItemProps) => { }; export const computeFlexItemProps = (props: FlexItemProps) => { - // prettier-ignore - const { - className, - style, - grow, - order, - shrink, - basis, - align, - ...rest - } = props; - // prettier-ignore - const computedBasis = basis + const { className, style, grow, order, shrink, basis, align, ...rest } = + props; + + const computedBasis = + basis ?? // IE11: Set basis to specified width if it's known, which fixes certain // bugs when rendering tables inside the flex. - ?? props.width + props.width ?? // If grow is used, basis should be set to 0 to be consistent with // flex css shorthand `flex: 1`. - ?? (grow !== undefined ? 0 : undefined); + (grow !== undefined ? 0 : undefined); + return computeBoxProps({ style: { ...style, - 'flex-grow': grow !== undefined && Number(grow), - 'flex-shrink': shrink !== undefined && Number(shrink), - 'flex-basis': unit(computedBasis), - 'order': order, - 'align-self': align, + flexGrow: grow !== undefined && Number(grow), + flexShrink: shrink !== undefined && Number(shrink), + flexBasis: unit(computedBasis), + order: order, + alignSelf: align, }, ...rest, }); @@ -110,6 +108,4 @@ const FlexItem = (props) => { ); }; -FlexItem.defaultHooks = pureComponentHooks; - Flex.Item = FlexItem; diff --git a/tgui/packages/tgui/components/Grid.jsx b/tgui/packages/tgui/components/Grid.jsx index 7ad3fd1dfd..f5593c9e00 100644 --- a/tgui/packages/tgui/components/Grid.jsx +++ b/tgui/packages/tgui/components/Grid.jsx @@ -5,7 +5,6 @@ */ import { Table } from './Table'; -import { pureComponentHooks } from 'common/react'; /** @deprecated */ export const Grid = (props) => { @@ -17,8 +16,6 @@ export const Grid = (props) => { ); }; -Grid.defaultHooks = pureComponentHooks; - /** @deprecated */ export const GridColumn = (props) => { const { size = 1, style, ...rest } = props; @@ -33,6 +30,4 @@ export const GridColumn = (props) => { ); }; -Grid.defaultHooks = pureComponentHooks; - Grid.Column = GridColumn; diff --git a/tgui/packages/tgui/components/Icon.tsx b/tgui/packages/tgui/components/Icon.tsx index da8b6af26a..cf0b55dfa8 100644 --- a/tgui/packages/tgui/components/Icon.tsx +++ b/tgui/packages/tgui/components/Icon.tsx @@ -6,40 +6,34 @@ * @license MIT */ -import { classes, pureComponentHooks } from 'common/react'; -import { InfernoNode } from 'inferno'; +import { BooleanLike, classes } from 'common/react'; +import { ReactNode } from 'react'; + import { BoxProps, computeBoxClassName, computeBoxProps } from './Box'; const FA_OUTLINE_REGEX = /-o$/; -type IconPropsUnique = { - name: string; - size?: number; - spin?: boolean; - className?: string; - rotation?: number; - style?: string | CSSProperties; -}; +type IconPropsUnique = { name: string } & Partial<{ + size: number; + spin: BooleanLike; + className: string; + rotation: number; + style: Partial; +}>; export type IconProps = IconPropsUnique & BoxProps; export const Icon = (props: IconProps) => { - let { style, ...restlet } = props; - const { name, size, spin, className, rotation, ...rest } = restlet; + const { name, size, spin, className, rotation, ...rest } = props; + const customStyle = rest.style || {}; if (size) { - if (!style) { - style = {}; - } - style['font-size'] = size * 100 + '%'; + customStyle.fontSize = size * 100 + '%'; } if (rotation) { - if (!style) { - style = {}; - } - style['transform'] = `rotate(${rotation}deg)`; + customStyle.transform = `rotate(${rotation}deg)`; } - rest.style = style; + rest.style = customStyle; const boxProps = computeBoxProps(rest); @@ -75,10 +69,8 @@ export const Icon = (props: IconProps) => { ); }; -Icon.defaultHooks = pureComponentHooks; - type IconStackUnique = { - children: InfernoNode; + children: ReactNode; className?: string; }; @@ -88,8 +80,9 @@ export const IconStack = (props: IconStackProps) => { const { className, children, ...rest } = props; return ( + className={classes(['IconStack', className, computeBoxClassName(rest)])} + {...computeBoxProps(rest)} + > {children} ); diff --git a/tgui/packages/tgui/components/Image.tsx b/tgui/packages/tgui/components/Image.tsx new file mode 100644 index 0000000000..3e1519bfbb --- /dev/null +++ b/tgui/packages/tgui/components/Image.tsx @@ -0,0 +1,50 @@ +import { ReactNode } from 'react'; + +import { BoxProps, computeBoxProps } from './Box'; +import { Tooltip } from './Tooltip'; + +type Props = Partial<{ + fixBlur: boolean; // true is default, this is an ie thing + objectFit: 'contain' | 'cover'; // fill is default + tooltip: ReactNode; +}> & + IconUnion & + BoxProps; + +// at least one of these is required +type IconUnion = + | { + className?: string; + src: string; + } + | { + className: string; + src?: string; + }; + +/** Image component. Use this instead of Box as="img". */ +export const Image = (props: Props) => { + const { + className, + fixBlur = true, + objectFit = 'fill', + src, + tooltip, + ...rest + } = props; + + const computedProps = computeBoxProps(rest); + computedProps['style'] = { + ...computedProps.style, + '-ms-interpolation-mode': fixBlur ? 'nearest-neighbor' : 'auto', + objectFit, + }; + + let content = ; + + if (tooltip) { + content = {content}; + } + + return content; +}; diff --git a/tgui/packages/tgui/components/InfinitePlane.jsx b/tgui/packages/tgui/components/InfinitePlane.jsx index 74f60f1e4d..5277f2ad79 100644 --- a/tgui/packages/tgui/components/InfinitePlane.jsx +++ b/tgui/packages/tgui/components/InfinitePlane.jsx @@ -1,8 +1,9 @@ +import { Component } from 'react'; + import { computeBoxProps } from './Box'; -import { Stack } from './Stack'; -import { ProgressBar } from './ProgressBar'; import { Button } from './Button'; -import { Component } from 'inferno'; +import { ProgressBar } from './ProgressBar'; +import { Stack } from './Stack'; const ZOOM_MIN_VAL = 0.5; const ZOOM_MAX_VAL = 1.5; @@ -10,8 +11,8 @@ const ZOOM_MAX_VAL = 1.5; const ZOOM_INCREMENT = 0.1; export class InfinitePlane extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.state = { mouseDown: false, @@ -139,30 +140,32 @@ export class InfinitePlane extends Component { overflow: 'hidden', position: 'relative', }, - })}> + })} + >
+ position: 'fixed', + transform: `translate(${finalLeft}px, ${finalTop}px) scale(${zoom})`, + transformOrigin: 'top left', + height: '100%', + width: '100%', + }} + > {children}
@@ -174,7 +177,8 @@ export class InfinitePlane extends Component { + maxValue={ZOOM_MAX_VAL} + > {zoom}x diff --git a/tgui/packages/tgui/components/Input.jsx b/tgui/packages/tgui/components/Input.jsx index ac7ce6eef3..252f1c917e 100644 --- a/tgui/packages/tgui/components/Input.jsx +++ b/tgui/packages/tgui/components/Input.jsx @@ -6,7 +6,8 @@ import { KEY_ENTER, KEY_ESCAPE } from 'common/keycodes'; import { classes } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; + import { Box } from './Box'; // prettier-ignore @@ -17,8 +18,8 @@ export const toInputValue = value => ( ); export class Input extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.inputRef = createRef(); this.state = { editing: false, @@ -138,7 +139,8 @@ export class Input extends Component { monospace && 'Input--monospace', className, ])} - {...rest}> + {...rest} + >
.
{ dispose: () => void; - constructor() { - super(); + constructor(props) { + super(props); this.dispose = listenForKeyEvents((key) => { if (this.props.onKey) { diff --git a/tgui/packages/tgui/components/Knob.jsx b/tgui/packages/tgui/components/Knob.jsx index 1d40ff1ed2..e9067fc71d 100644 --- a/tgui/packages/tgui/components/Knob.jsx +++ b/tgui/packages/tgui/components/Knob.jsx @@ -6,16 +6,11 @@ import { keyOfMatchingRange, scale } from 'common/math'; import { classes } from 'common/react'; + import { computeBoxClassName, computeBoxProps } from './Box'; import { DraggableControl } from './DraggableControl'; -import { NumberInput } from './NumberInput'; export const Knob = (props) => { - // IE8: I don't want to support a yet another component on IE8. - // IE8: It also can't handle SVG. - if (Byond.IS_LTE_IE8) { - return ; - } const { // Draggable props (passthrough) animated, @@ -57,7 +52,8 @@ export const Knob = (props) => { suppressFlicker, unit, value, - }}> + }} + > {(control) => { const { dragging, @@ -71,7 +67,7 @@ export const Knob = (props) => { const scaledFillValue = scale( fillValue ?? displayValue, minValue, - maxValue + maxValue, ); const scaledDisplayValue = scale(displayValue, minValue, maxValue); const effectiveColor = @@ -88,18 +84,20 @@ export const Knob = (props) => { ])} {...computeBoxProps({ style: { - 'font-size': size + 'em', + fontSize: size + 'em', ...style, }, ...rest, })} - onMouseDown={handleDragStart}> + onMouseDown={handleDragStart} + >
+ }} + >
@@ -108,20 +106,22 @@ export const Knob = (props) => { )} + viewBox="0 0 100 100" + > + viewBox="0 0 100 100" + > { +export function LabeledControls(props: FlexProps) { const { children, wrap, ...rest } = props; + return ( + {...rest} + > {children} ); -}; +} -const LabeledControlsItem = (props) => { +type ItemProps = { + label: string; +} & FlexProps; + +function LabeledControlsItem(props: ItemProps) { const { label, children, mx = 1, ...rest } = props; + return ( { align="center" textAlign="center" justify="space-between" - {...rest}> + {...rest} + > {children} {label} ); -}; +} LabeledControls.Item = LabeledControlsItem; diff --git a/tgui/packages/tgui/components/LabeledList.tsx b/tgui/packages/tgui/components/LabeledList.tsx index 283c0a2b91..2c2baa9c34 100644 --- a/tgui/packages/tgui/components/LabeledList.tsx +++ b/tgui/packages/tgui/components/LabeledList.tsx @@ -4,35 +4,33 @@ * @license MIT */ -import { BooleanLike, classes, pureComponentHooks } from 'common/react'; -import { InfernoNode } from 'inferno'; +import { BooleanLike, classes } from 'common/react'; +import { PropsWithChildren, ReactNode } from 'react'; + import { Box, unit } from './Box'; import { Divider } from './Divider'; +import { Tooltip } from './Tooltip'; -type LabeledListProps = { - children?: any; -}; - -export const LabeledList = (props: LabeledListProps) => { +export const LabeledList = (props: PropsWithChildren) => { const { children } = props; return {children}
; }; -LabeledList.defaultHooks = pureComponentHooks; - -type LabeledListItemProps = { - className?: string | BooleanLike; - label?: string | InfernoNode | BooleanLike; - labelColor?: string | BooleanLike; - labelWrap?: boolean; - color?: string | BooleanLike; - textAlign?: string | BooleanLike; - buttons?: InfernoNode; +type LabeledListItemProps = Partial<{ + buttons: ReactNode; + className: string | BooleanLike; + color: string; + key: string | number; + label: string | ReactNode | BooleanLike; + labelColor: string; + labelWrap: boolean; + textAlign: string; /** @deprecated */ - content?: any; - children?: InfernoNode; - verticalAlign?: string; -}; + content: any; + children: ReactNode; + verticalAlign: string; + tooltip: string; +}>; const LabeledListItem = (props: LabeledListItemProps) => { const { @@ -46,27 +44,56 @@ const LabeledListItem = (props: LabeledListItemProps) => { content, children, verticalAlign = 'baseline', + tooltip, } = props; + + let innerLabel; + if (label) { + innerLabel = label; + if (typeof label === 'string') innerLabel += ':'; + } + + if (tooltip !== undefined) { + innerLabel = ( + + + {innerLabel} + + + ); + } + + let labelChild = ( + + {innerLabel} + + ); + return ( - - {label ? (typeof label === 'string' ? label + ':' : label) : null} - + {labelChild} + verticalAlign={verticalAlign} + > {content} {children} @@ -77,8 +104,6 @@ const LabeledListItem = (props: LabeledListItemProps) => { ); }; -LabeledListItem.defaultHooks = pureComponentHooks; - type LabeledListDividerProps = { size?: number; }; @@ -90,16 +115,15 @@ const LabeledListDivider = (props: LabeledListDividerProps) => { + paddingTop: padding, + paddingBottom: padding, + }} + > ); }; -LabeledListDivider.defaultHooks = pureComponentHooks; - LabeledList.Item = LabeledListItem; LabeledList.Divider = LabeledListDivider; diff --git a/tgui/packages/tgui/components/MenuBar.tsx b/tgui/packages/tgui/components/MenuBar.tsx index 35de61ec9c..acf35e8281 100644 --- a/tgui/packages/tgui/components/MenuBar.tsx +++ b/tgui/packages/tgui/components/MenuBar.tsx @@ -5,9 +5,10 @@ */ import { classes } from 'common/react'; -import { Component, createRef, InfernoNode, RefObject } from 'inferno'; -import { Box } from './Box'; +import { Component, createRef, ReactNode, RefObject } from 'react'; + import { logger } from '../logging'; +import { Box } from './Box'; import { Icon } from './Icon'; type MenuProps = { @@ -53,7 +54,8 @@ class Menu extends Component { className={'MenuBar__menu'} style={{ width: width, - }}> + }} + > {children}
); @@ -105,15 +107,17 @@ class MenuBarButton extends Component { className, ])} {...rest} - onClick={disabled ? undefined : onClick} - onmouseover={onMouseOver}> + onClick={disabled ? () => null : onClick} + onMouseOver={onMouseOver} + > {display} {open && ( + onOutsideClick={onOutsideClick} + > {children} )} @@ -126,7 +130,7 @@ type MenuBarItemProps = { entry: string; children: any; openWidth: string; - display: InfernoNode; + display: ReactNode; setOpenMenuBar: (entry: string | null) => void; openMenuBar: string | null; setOpenOnHover: (flag: boolean) => void; @@ -169,7 +173,8 @@ export const Dropdown = (props: MenuBarItemProps) => { if (openOnHover) { setOpenMenuBar(entry); } - }}> + }} + > {children} ); @@ -185,7 +190,8 @@ const MenuItemToggle = (props) => { 'MenuBar__MenuItemToggle', 'MenuBar__hover', ])} - onClick={() => onClick(value)}> + onClick={() => onClick(value)} + >
{checked && }
@@ -205,7 +211,8 @@ const MenuItem = (props) => { 'MenuBar__MenuItem', 'MenuBar__hover', ])} - onClick={() => onClick(value)}> + onClick={() => onClick(value)} + > {displayText} ); diff --git a/tgui/packages/tgui/components/Modal.jsx b/tgui/packages/tgui/components/Modal.jsx index 54c8c090ec..69231ec87f 100644 --- a/tgui/packages/tgui/components/Modal.jsx +++ b/tgui/packages/tgui/components/Modal.jsx @@ -5,6 +5,7 @@ */ import { classes } from 'common/react'; + import { computeBoxClassName, computeBoxProps } from './Box'; import { Dimmer } from './Dimmer'; @@ -25,7 +26,8 @@ export const Modal = (props) => {
+ {...computeBoxProps(rest)} + > {children}
diff --git a/tgui/packages/tgui/components/NanoMap.jsx b/tgui/packages/tgui/components/NanoMap.jsx index 5c10f7a73f..d2e15a71f2 100644 --- a/tgui/packages/tgui/components/NanoMap.jsx +++ b/tgui/packages/tgui/components/NanoMap.jsx @@ -1,6 +1,7 @@ -import { Component } from 'inferno'; -import { Box, Button, Icon, Tooltip, LabeledList, Slider } from '.'; +import { Component } from 'react'; + import { useBackend } from '../backend'; +import { Box, Button, Icon, LabeledList, Slider, Tooltip } from '.'; const pauseEvent = (e) => { if (e.stopPropagation) { @@ -132,13 +133,13 @@ export class NanoMap extends Component { height: mapSize, 'margin-top': offsetY + 'px', 'margin-left': offsetX + 'px', - 'overflow': 'hidden', - 'position': 'relative', + overflow: 'hidden', + position: 'relative', 'background-image': 'url(' + mapUrl + ')', 'background-size': 'cover', 'background-repeat': 'no-repeat', 'text-align': 'center', - 'cursor': dragging ? 'move' : 'auto', + cursor: dragging ? 'move' : 'auto', }; return ( @@ -147,7 +148,8 @@ export class NanoMap extends Component { style={newStyle} textAlign="center" onMouseDown={this.handleDragStart} - onClick={this.handleOnClick}> + onClick={this.handleOnClick} + > {children} @@ -176,7 +178,8 @@ const NanoMapMarker = (props, context) => { lineHeight="0" bottom={ry + 'px'} left={rx + 'px'} - onMouseDown={handleOnClick}> + onMouseDown={handleOnClick} + > @@ -186,8 +189,8 @@ const NanoMapMarker = (props, context) => { NanoMap.Marker = NanoMapMarker; -const NanoMapZoomer = (props, context) => { - const { act, config, data } = useBackend(context); +const NanoMapZoomer = (props) => { + const { act, config, data } = useBackend(); return ( @@ -210,7 +213,7 @@ const NanoMapZoomer = (props, context) => { selected={~~level === ~~config.mapZLevel} content={level} onClick={() => { - act('setZLevel', { 'mapZLevel': level }); + act('setZLevel', { mapZLevel: level }); }} /> ))} diff --git a/tgui/packages/tgui/components/NoticeBox.jsx b/tgui/packages/tgui/components/NoticeBox.jsx deleted file mode 100644 index 1c3b49b16a..0000000000 --- a/tgui/packages/tgui/components/NoticeBox.jsx +++ /dev/null @@ -1,27 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -import { classes, pureComponentHooks } from 'common/react'; -import { Box } from './Box'; - -export const NoticeBox = (props) => { - const { className, color, info, warning, success, danger, ...rest } = props; - return ( - - ); -}; - -NoticeBox.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui/components/NoticeBox.tsx b/tgui/packages/tgui/components/NoticeBox.tsx new file mode 100644 index 0000000000..db4f821d6b --- /dev/null +++ b/tgui/packages/tgui/components/NoticeBox.tsx @@ -0,0 +1,48 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +import { classes } from 'common/react'; + +import { Box, BoxProps } from './Box'; + +type Props = ExclusiveProps & BoxProps; + +/** You MUST use only one or none */ +type NoticeType = 'info' | 'success' | 'danger'; + +type None = { + [K in NoticeType]?: undefined; +}; + +type ExclusiveProps = + | None + | (Omit & { + info: boolean; + }) + | (Omit & { + success: boolean; + }) + | (Omit & { + danger: boolean; + }); + +export function NoticeBox(props: Props) { + const { className, color, info, success, danger, ...rest } = props; + + return ( + + ); +} diff --git a/tgui/packages/tgui/components/NumberInput.jsx b/tgui/packages/tgui/components/NumberInput.jsx index e264d811d3..f5579731c0 100644 --- a/tgui/packages/tgui/components/NumberInput.jsx +++ b/tgui/packages/tgui/components/NumberInput.jsx @@ -5,8 +5,9 @@ */ import { clamp } from 'common/math'; -import { classes, pureComponentHooks } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { classes } from 'common/react'; +import { Component, createRef } from 'react'; + import { AnimatedNumber } from './AnimatedNumber'; import { Box } from './Box'; @@ -40,7 +41,7 @@ export class NumberInput extends Component { this.setState({ suppressingFlicker: false, }), - suppressFlicker + suppressFlicker, ); } }; @@ -87,13 +88,13 @@ export class NumberInput extends Component { state.internalValue = clamp( state.internalValue + (offset * step) / stepPixelSize, minValue - step, - maxValue + step + maxValue + step, ); // Clamp the final value state.value = clamp( state.internalValue - (state.internalValue % step) + stepOffset, minValue, - maxValue + maxValue, ); state.origin = e.screenY; } else if (Math.abs(offset) > 4) { @@ -165,14 +166,15 @@ export class NumberInput extends Component { displayValue = intermediateValue; } - // prettier-ignore const contentElement = ( -
- { - (animated && !dragging && !suppressingFlicker) ? - () : - (format ? format(displayValue) : displayValue) - } +
+ {animated && !dragging && !suppressingFlicker ? ( + + ) : format ? ( + format(displayValue) + ) : ( + displayValue + )} {unit ? ' ' + unit : ''}
@@ -189,15 +191,18 @@ export class NumberInput extends Component { minHeight={height} lineHeight={lineHeight} fontSize={fontSize} - onMouseDown={this.handleDragStart}> + onMouseDown={this.handleDragStart} + >
@@ -208,8 +213,8 @@ export class NumberInput extends Component { style={{ display: !editing ? 'none' : undefined, height: height, - 'line-height': lineHeight, - 'font-size': fontSize, + lineHeight: lineHeight, + fontSize: fontSize, }} onBlur={(e) => { if (!editing) { @@ -274,7 +279,6 @@ export class NumberInput extends Component { } } -NumberInput.defaultHooks = pureComponentHooks; NumberInput.defaultProps = { minValue: -Infinity, maxValue: +Infinity, diff --git a/tgui/packages/tgui/components/Popper.tsx b/tgui/packages/tgui/components/Popper.tsx index 907896d6b8..dbc7c18f7b 100644 --- a/tgui/packages/tgui/components/Popper.tsx +++ b/tgui/packages/tgui/components/Popper.tsx @@ -1,79 +1,96 @@ -import { createPopper } from '@popperjs/core'; -import { ArgumentsOf } from 'common/types'; -import { Component, findDOMfromVNode, InfernoNode, render } from 'inferno'; +import { Placement } from '@popperjs/core'; +import { + PropsWithChildren, + ReactNode, + useEffect, + useRef, + useState, +} from 'react'; +import { usePopper } from 'react-popper'; -type PopperProps = { - popperContent: InfernoNode; - options?: ArgumentsOf[2]; - additionalStyles?: CSSProperties; +type RequiredProps = { + /** The content to display in the popper */ + content: ReactNode; + /** Whether the popper is open */ + isOpen: boolean; }; -export class Popper extends Component { - static id: number = 0; +type OptionalProps = Partial<{ + /** Called when the user clicks outside the popper */ + onClickOutside: () => void; + /** Where to place the popper relative to the reference element */ + placement: Placement; +}>; - renderedContent: HTMLDivElement; - popperInstance: ReturnType; +type Props = RequiredProps & OptionalProps; - constructor() { - super(); +/** + * ## Popper + * Popper lets you position elements so that they don't go out of the bounds of the window. + * @url https://popper.js.org/react-popper/ for more information. + */ +export function Popper(props: PropsWithChildren) { + const { children, content, isOpen, onClickOutside, placement } = props; - Popper.id += 1; + const [referenceElement, setReferenceElement] = + useState(null); + const [popperElement, setPopperElement] = useState( + null, + ); + + // One would imagine we could just use useref here, but it's against react-popper documentation and causes a positioning bug + // We still need them to call focus and clickoutside events :( + const popperRef = useRef(null); + const parentRef = useRef(null); + + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement, + }); + + /** Close the popper when the user clicks outside */ + function handleClickOutside(event: MouseEvent) { + if ( + !popperRef.current?.contains(event.target as Node) && + !parentRef.current?.contains(event.target as Node) + ) { + onClickOutside?.(); + } } - componentDidMount() { - const { additionalStyles, options } = this.props; - - this.renderedContent = document.createElement('div'); - - if (additionalStyles) { - for (const [attribute, value] of Object.entries(additionalStyles)) { - this.renderedContent.style[attribute] = value; - } + useEffect(() => { + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + } else { + document.removeEventListener('mousedown', handleClickOutside); } - this.renderPopperContent(() => { - document.body.appendChild(this.renderedContent); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isOpen]); - // HACK: We don't want to create a wrapper, as it could break the layout - // of consumers, so we do the inferno equivalent of `findDOMNode(this)`. - // This is usually bad as refs are usually better, but refs did - // not work in this case, as they weren't propagating correctly. - // A previous attempt was made as a render prop that passed an ID, - // but this made consuming use too unwieldly. - // This code is copied from `findDOMNode` in inferno-extras. - // Because this component is written in TypeScript, we will know - // immediately if this internal variable is removed. - const domNode = findDOMfromVNode(this.$LI, true); - if (!domNode) { - return; - } - - this.popperInstance = createPopper( - domNode, - this.renderedContent, - options - ); - }); - } - - componentDidUpdate() { - this.renderPopperContent(() => this.popperInstance?.update()); - } - - componentWillUnmount() { - this.popperInstance?.destroy(); - render(null, this.renderedContent, () => { - this.renderedContent.remove(); - }); - } - - renderPopperContent(callback: () => void) { - // `render` errors when given false, so we convert it to `null`, - // which is supported. - render(this.props.popperContent || null, this.renderedContent, callback); - } - - render() { - return this.props.children; - } + return ( + <> +
{ + setReferenceElement(node); + parentRef.current = node; + }} + > + {children} +
+ {isOpen && ( +
{ + setPopperElement(node); + popperRef.current = node; + }} + style={{ ...styles.popper, zIndex: 5 }} + {...attributes.popper} + > + {content} +
+ )} + + ); } diff --git a/tgui/packages/tgui/components/ProgressBar.jsx b/tgui/packages/tgui/components/ProgressBar.tsx similarity index 50% rename from tgui/packages/tgui/components/ProgressBar.jsx rename to tgui/packages/tgui/components/ProgressBar.tsx index 86116732c3..ce2f071ba0 100644 --- a/tgui/packages/tgui/components/ProgressBar.jsx +++ b/tgui/packages/tgui/components/ProgressBar.tsx @@ -4,12 +4,31 @@ * @license MIT */ -import { clamp01, scale, keyOfMatchingRange, toFixed } from 'common/math'; -import { classes, pureComponentHooks } from 'common/react'; -import { computeBoxClassName, computeBoxProps } from './Box'; -import { CSS_COLORS } from '../constants'; +import { clamp01, keyOfMatchingRange, scale, toFixed } from 'common/math'; +import { classes } from 'common/react'; +import { PropsWithChildren } from 'react'; -export const ProgressBar = (props) => { +import { CSS_COLORS } from '../constants'; +import { BoxProps, computeBoxClassName, computeBoxProps } from './Box'; + +type Props = { + value: number; +} & Partial<{ + backgroundColor: string; + className: string; + color: string; + height: string | number; + maxValue: number; + minValue: number; + ranges: Record; + style: Partial; + title: string; + width: string | number; +}> & + Partial & + PropsWithChildren; + +export const ProgressBar = (props: Props) => { const { className, value, @@ -22,32 +41,28 @@ export const ProgressBar = (props) => { } = props; const scaledValue = scale(value, minValue, maxValue); const hasContent = children !== undefined; - // prettier-ignore - const effectiveColor = color - || keyOfMatchingRange(value, ranges) - || 'default'; + + const effectiveColor = + color || keyOfMatchingRange(value, ranges) || 'default'; // We permit colors to be in hex format, rgb()/rgba() format, // a name for a color- class, or a base CSS class. const outerProps = computeBoxProps(rest); - // prettier-ignore - const outerClasses = [ - 'ProgressBar', - className, - computeBoxClassName(rest), - ]; + + const outerClasses = ['ProgressBar', className, computeBoxClassName(rest)]; const fillStyles = { - 'width': clamp01(scaledValue) * 100 + '%', + width: clamp01(scaledValue) * 100 + '%', }; - if (CSS_COLORS.includes(effectiveColor) || effectiveColor === 'default') { + if ( + CSS_COLORS.includes(effectiveColor as any) || + effectiveColor === 'default' + ) { // If the color is a color- class, just use that. outerClasses.push('ProgressBar--color--' + effectiveColor); } else { // Otherwise, set styles directly. - // prettier-ignore - outerProps.style = (outerProps.style || "") - + `border-color: ${effectiveColor};`; - fillStyles['background-color'] = effectiveColor; + outerProps.style = { ...outerProps.style, borderColor: effectiveColor }; + fillStyles['backgroundColor'] = effectiveColor; } return ( @@ -62,5 +77,3 @@ export const ProgressBar = (props) => {
); }; - -ProgressBar.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui/components/RestrictedInput.jsx b/tgui/packages/tgui/components/RestrictedInput.jsx index 1b082296ca..3125545bae 100644 --- a/tgui/packages/tgui/components/RestrictedInput.jsx +++ b/tgui/packages/tgui/components/RestrictedInput.jsx @@ -1,8 +1,9 @@ -import { classes } from 'common/react'; +import { KEY_ENTER, KEY_ESCAPE } from 'common/keycodes'; import { clamp } from 'common/math'; -import { Component, createRef } from 'inferno'; +import { classes } from 'common/react'; +import { Component, createRef } from 'react'; + import { Box } from './Box'; -import { KEY_ESCAPE, KEY_ENTER } from 'common/keycodes'; const DEFAULT_MIN = 0; const DEFAULT_MAX = 10000; @@ -29,8 +30,8 @@ const getClampedNumber = (value, minValue, maxValue, allowFloats) => { }; export class RestrictedInput extends Component { - constructor() { - super(); + constructor(props) { + super(props); this.inputRef = createRef(); this.state = { editing: false, @@ -47,7 +48,7 @@ export class RestrictedInput extends Component { e.target.value, minValue, maxValue, - allowFloats + allowFloats, ); if (onChange) { onChange(e, +e.target.value); @@ -76,7 +77,7 @@ export class RestrictedInput extends Component { e.target.value, minValue, maxValue, - allowFloats + allowFloats, ); this.setEditing(false); if (onChange) { @@ -110,7 +111,7 @@ export class RestrictedInput extends Component { nextValue, minValue, maxValue, - allowFloats + allowFloats, ); } if (this.props.autoFocus || this.props.autoSelect) { @@ -136,7 +137,7 @@ export class RestrictedInput extends Component { nextValue, minValue, maxValue, - allowFloats + allowFloats, ); } } @@ -158,7 +159,8 @@ export class RestrictedInput extends Component { monospace && 'Input--monospace', className, ])} - {...rest}> + {...rest} + >
.
{ - // Support for IE8 is for losers sorry B) - if (Byond.IS_LTE_IE8) { - return ; - } - const { value, minValue = 1, @@ -31,7 +27,7 @@ export const RoundGauge = (props) => { const scaledValue = scale(value, minValue, maxValue); const clampedValue = clamp01(scaledValue); - const scaledRanges = ranges ? {} : { 'primary': [0, 1] }; + const scaledRanges = ranges ? {} : { primary: [0, 1] }; if (ranges) { Object.keys(ranges).forEach((x) => { const range = ranges[x]; @@ -73,18 +69,20 @@ export const RoundGauge = (props) => { ])} {...computeBoxProps({ style: { - 'font-size': size + 'em', + fontSize: size + 'em', ...style, }, ...rest, - })}> + })} + > {(alertAfter || alertBefore) && ( + ])} + > )} @@ -99,9 +97,9 @@ export const RoundGauge = (props) => { className={`RoundGauge__ringFill RoundGauge--color--${x}`} key={i} style={{ - 'stroke-dashoffset': Math.max( + strokeDashoffset: Math.max( (2.0 - (col_ranges[1] - col_ranges[0])) * Math.PI * 50, - 0 + 0, ), }} transform={`rotate(${180 + 180 * col_ranges[0]} 50 50)`} @@ -114,7 +112,8 @@ export const RoundGauge = (props) => { + transform={`rotate(${clampedValue * 180 - 90} 50 50)`} + > ; - buttons?: InfernoNode; - fill?: boolean; - fitted?: boolean; - scrollable?: boolean; - scrollableHorizontal?: boolean; +type Props = Partial<{ + /** Buttons to render aside the section title. */ + buttons: ReactNode; + /** If true, fills all available vertical space. */ + fill: boolean; + /** If true, removes all section padding. */ + fitted: boolean; + /** Shows or hides the scrollbar. */ + scrollable: boolean; + /** Shows or hides the horizontal scrollbar. */ + scrollableHorizontal: boolean; + /** Title of the section. */ + title: ReactNode; + /** @member Callback function for the `scroll` event */ + onScroll: ((this: GlobalEventHandlers, ev: Event) => any) | null; + flexGrow?: boolean; // VOREStation Addition noTopPadding?: boolean; // VOREStation Addition stretchContents?: boolean; // VOREStation Addition - /** @deprecated This property no longer works, please remove it. */ - level?: never; - /** @deprecated Please use `scrollable` property */ - overflowY?: never; - /** @member Allows external control of scrolling. */ - scrollableRef?: RefObject; - /** @member Callback function for the `scroll` event */ - onScroll?: (this: GlobalEventHandlers, ev: Event) => any; -} +}> & + BoxProps; -export class Section extends Component { - scrollableRef: RefObject; - scrollable: boolean; - onScroll?: (this: GlobalEventHandlers, ev: Event) => any; - scrollableHorizontal: boolean; - - constructor(props) { - super(props); - this.scrollableRef = props.scrollableRef || createRef(); - this.scrollable = props.scrollable; - this.onScroll = props.onScroll; - this.scrollableHorizontal = props.scrollableHorizontal; - } - - componentDidMount() { - if (this.scrollable || this.scrollableHorizontal) { - addScrollableNode(this.scrollableRef.current as HTMLElement); - if (this.onScroll && this.scrollableRef.current) { - this.scrollableRef.current.onscroll = this.onScroll; - } - } - } - - componentWillUnmount() { - if (this.scrollable || this.scrollableHorizontal) { - removeScrollableNode(this.scrollableRef.current as HTMLElement); - } - } - - render() { +/** + * ## Section + * Section is a surface that displays content and actions on a single topic. + * + * They should be easy to scan for relevant and actionable information. + * Elements, like text and images, should be placed in them in a way that + * clearly indicates hierarchy. + * + * Sections can now be nested, and will automatically font size of the + * header according to their nesting level. Previously this was done via `level` + * prop, but now it is automatically calculated. + * + * Section can also be titled to clearly define its purpose. + * + * ```tsx + *
Here you can order supply crates.
+ * ``` + * + * If you want to have a button on the right side of an section title + * (for example, to perform some sort of action), there is a way to do that: + * + * ```tsx + *
Send shuttle}> + * Here you can order supply crates. + *
+ * ``` + */ +export const Section = forwardRef( + (props: Props, forwardedRef: RefObject) => { const { - className, - title, buttons, + children, + className, fill, fitted, + onScroll, scrollable, scrollableHorizontal, + title, flexGrow, // VOREStation Addition noTopPadding, // VOREStation Addition stretchContents, // VOREStation Addition - children, - onScroll, ...rest - } = this.props; + } = props; + const hasTitle = canRender(title) || canRender(buttons); + + /** We want to be able to scroll on hover, but using focus will steal it from inputs */ + useEffect(() => { + if (!forwardedRef?.current) return; + if (!scrollable && !scrollableHorizontal) return; + + addScrollableNode(forwardedRef.current); + + return () => { + if (!forwardedRef?.current) return; + removeScrollableNode(forwardedRef.current!); + }; + }, []); + return (
{ className, computeBoxClassName(rest), ])} - {...computeBoxProps(rest)}> + {...computeBoxProps(rest)} + > {hasTitle && (
{title} @@ -97,20 +113,21 @@ export class Section extends Component {
)}
- {/* Vorestation Edit Start */}
+ !!stretchContents && 'Section__content--stretchContents', // VOREStation Addition + !!noTopPadding && 'Section__content--noTopPadding', // VOREStation Addition + ])} + onScroll={onScroll} + // For posterity: the forwarded ref needs to be here specifically + // to actually let things interact with the scrolling. + ref={forwardedRef} + > {children}
- {/* Vorestation Edit End */}
); - } -} + }, +); diff --git a/tgui/packages/tgui/components/Slider.jsx b/tgui/packages/tgui/components/Slider.jsx index 84ee8421a9..7f1aa457c7 100644 --- a/tgui/packages/tgui/components/Slider.jsx +++ b/tgui/packages/tgui/components/Slider.jsx @@ -6,15 +6,11 @@ import { clamp01, keyOfMatchingRange, scale } from 'common/math'; import { classes } from 'common/react'; + import { computeBoxClassName, computeBoxProps } from './Box'; import { DraggableControl } from './DraggableControl'; -import { NumberInput } from './NumberInput'; export const Slider = (props) => { - // IE8: I don't want to support a yet another component on IE8. - if (Byond.IS_LTE_IE8) { - return ; - } const { // Draggable props (passthrough) animated, @@ -52,7 +48,8 @@ export const Slider = (props) => { suppressFlicker, unit, value, - }}> + }} + > {(control) => { const { dragging, @@ -68,7 +65,7 @@ export const Slider = (props) => { const scaledFillValue = scale( fillValue ?? displayValue, minValue, - maxValue + maxValue, ); const scaledDisplayValue = scale(displayValue, minValue, maxValue); // prettier-ignore @@ -84,7 +81,8 @@ export const Slider = (props) => { computeBoxClassName(rest), ])} {...computeBoxProps(rest)} - onMouseDown={handleDragStart}> + onMouseDown={handleDragStart} + >
{ className="Slider__cursorOffset" style={{ width: clamp01(scaledDisplayValue) * 100 + '%', - }}> + }} + >
{dragging && ( diff --git a/tgui/packages/tgui/components/Stack.tsx b/tgui/packages/tgui/components/Stack.tsx index 9abf13e651..18ae34c09e 100644 --- a/tgui/packages/tgui/components/Stack.tsx +++ b/tgui/packages/tgui/components/Stack.tsx @@ -5,22 +5,33 @@ */ import { classes } from 'common/react'; -import { RefObject } from 'inferno'; -import { computeFlexClassName, computeFlexItemClassName, computeFlexItemProps, computeFlexProps, FlexItemProps, FlexProps } from './Flex'; +import { RefObject } from 'react'; -type StackProps = FlexProps & { - vertical?: boolean; - fill?: boolean; -}; +import { + computeFlexClassName, + computeFlexItemClassName, + computeFlexItemProps, + computeFlexProps, + FlexItemProps, + FlexProps, +} from './Flex'; -export const Stack = (props: StackProps) => { - const { className, vertical, fill, ...rest } = props; +type Props = Partial<{ + vertical: boolean; + fill: boolean; + zebra: boolean; +}> & + FlexProps; + +export const Stack = (props: Props) => { + const { className, vertical, fill, zebra, ...rest } = props; return (
{ ); }; -type StackItemProps = FlexProps & { - innerRef?: RefObject; -}; +type StackItemProps = FlexItemProps & + Partial<{ + innerRef: RefObject; + }>; const StackItem = (props: StackItemProps) => { const { className, innerRef, ...rest } = props; @@ -53,9 +65,10 @@ const StackItem = (props: StackItemProps) => { Stack.Item = StackItem; -type StackDividerProps = FlexItemProps & { - hidden?: boolean; -}; +type StackDividerProps = FlexItemProps & + Partial<{ + hidden: boolean; + }>; const StackDivider = (props: StackDividerProps) => { const { className, hidden, ...rest } = props; diff --git a/tgui/packages/tgui/components/StyleableSection.tsx b/tgui/packages/tgui/components/StyleableSection.tsx index ad38984fee..53b193707f 100644 --- a/tgui/packages/tgui/components/StyleableSection.tsx +++ b/tgui/packages/tgui/components/StyleableSection.tsx @@ -1,25 +1,29 @@ -import { SFC } from 'inferno'; +import { PropsWithChildren, ReactNode } from 'react'; + import { Box } from './Box'; +type Props = Partial<{ + style: Record; + titleStyle: Record; + textStyle: Record; + title: ReactNode; + titleSubtext: string; +}> & + PropsWithChildren; + // The cost of flexibility and prettiness. -export const StyleableSection: SFC<{ - style?; - titleStyle?; - textStyle?; - title?; - titleSubtext?; -}> = (props) => { +export const StyleableSection = (props: Props) => { return ( {/* Yes, this box (line above) is missing the "Section" class. This is very intentional, as the layout looks *ugly* with it.*/} - - + + {props.title}
{props.titleSubtext}
- - {props.children} + + {props.children} ); diff --git a/tgui/packages/tgui/components/Table.jsx b/tgui/packages/tgui/components/Table.jsx index e936b91448..31a0a82d04 100644 --- a/tgui/packages/tgui/components/Table.jsx +++ b/tgui/packages/tgui/components/Table.jsx @@ -4,7 +4,8 @@ * @license MIT */ -import { classes, pureComponentHooks } from 'common/react'; +import { classes } from 'common/react'; + import { computeBoxClassName, computeBoxProps } from './Box'; export const Table = (props) => { @@ -17,14 +18,13 @@ export const Table = (props) => { className, computeBoxClassName(rest), ])} - {...computeBoxProps(rest)}> + {...computeBoxProps(rest)} + > {children} ); }; -Table.defaultHooks = pureComponentHooks; - export const TableRow = (props) => { const { className, header, ...rest } = props; return ( @@ -40,8 +40,6 @@ export const TableRow = (props) => { ); }; -TableRow.defaultHooks = pureComponentHooks; - export const TableCell = (props) => { const { className, collapsing, header, ...rest } = props; return ( @@ -58,7 +56,5 @@ export const TableCell = (props) => { ); }; -TableCell.defaultHooks = pureComponentHooks; - Table.Row = TableRow; Table.Cell = TableCell; diff --git a/tgui/packages/tgui/components/Tabs.jsx b/tgui/packages/tgui/components/Tabs.tsx similarity index 63% rename from tgui/packages/tgui/components/Tabs.jsx rename to tgui/packages/tgui/components/Tabs.tsx index 46d1b078a8..84779bcf74 100644 --- a/tgui/packages/tgui/components/Tabs.jsx +++ b/tgui/packages/tgui/components/Tabs.tsx @@ -5,11 +5,35 @@ */ import { canRender, classes } from 'common/react'; -import { computeBoxClassName, computeBoxProps } from './Box'; +import { PropsWithChildren, ReactNode } from 'react'; + +import { BoxProps, computeBoxClassName, computeBoxProps } from './Box'; import { Icon } from './Icon'; -export const Tabs = (props) => { +type Props = Partial<{ + className: string; + fill: boolean; + fluid: boolean; + vertical: boolean; +}> & + BoxProps & + PropsWithChildren; + +type TabProps = Partial<{ + className: string; + color: string; + icon: string; + leftSlot: ReactNode; + onClick: (e?) => void; + rightSlot: ReactNode; + selected: boolean; +}> & + BoxProps & + PropsWithChildren; + +export const Tabs = (props: Props) => { const { className, vertical, fill, fluid, children, ...rest } = props; + return (
{ className, computeBoxClassName(rest), ])} - {...computeBoxProps(rest)}> + {...computeBoxProps(rest)} + > {children}
); }; -const Tab = (props) => { +const Tab = (props: TabProps) => { const { className, selected, @@ -37,6 +62,7 @@ const Tab = (props) => { children, ...rest } = props; + return (
{ 'Tab--color--' + color, selected && 'Tab--selected', className, - ...computeBoxClassName(rest), + computeBoxClassName(rest), ])} - {...computeBoxProps(rest)}> + {...computeBoxProps(rest)} + > {(canRender(leftSlot) &&
{leftSlot}
) || (!!icon && (
diff --git a/tgui/packages/tgui/components/TextArea.jsx b/tgui/packages/tgui/components/TextArea.jsx index 4fdf554242..9c32d964ed 100644 --- a/tgui/packages/tgui/components/TextArea.jsx +++ b/tgui/packages/tgui/components/TextArea.jsx @@ -5,11 +5,12 @@ * @license MIT */ +import { KEY_ENTER, KEY_ESCAPE, KEY_TAB } from 'common/keycodes'; import { classes } from 'common/react'; -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; + import { Box } from './Box'; import { toInputValue } from './Input'; -import { KEY_ENTER, KEY_ESCAPE, KEY_TAB } from 'common/keycodes'; export class TextArea extends Component { constructor(props) { @@ -185,6 +186,7 @@ export class TextArea extends Component { displayedValue, ...boxProps } = this.props; + // Box props const { className, fluid, nowrap, ...rest } = boxProps; const { scrolledAmount } = this.state; @@ -196,7 +198,8 @@ export class TextArea extends Component { noborder && 'TextArea--noborder', className, ])} - {...rest}> + {...rest} + > {!!displayedValue && (
+ transform: `translateY(-${scrolledAmount}px)`, + }} + > {displayedValue}
@@ -228,7 +232,7 @@ export class TextArea extends Component { onScroll={this.handleScroll} maxLength={maxLength} style={{ - 'color': displayedValue ? 'rgba(0, 0, 0, 0)' : 'inherit', + color: displayedValue ? 'rgba(0, 0, 0, 0)' : 'inherit', }} /> diff --git a/tgui/packages/tgui/components/TimeDisplay.jsx b/tgui/packages/tgui/components/TimeDisplay.jsx index 6b87ee5260..390dbc93af 100644 --- a/tgui/packages/tgui/components/TimeDisplay.jsx +++ b/tgui/packages/tgui/components/TimeDisplay.jsx @@ -1,5 +1,6 @@ +import { Component } from 'react'; + import { formatTime } from '../format'; -import { Component } from 'inferno'; // AnimatedNumber Copypaste const isSafeNumber = (value) => { diff --git a/tgui/packages/tgui/components/Tooltip.tsx b/tgui/packages/tgui/components/Tooltip.tsx index cb35425e74..f3936f32c6 100644 --- a/tgui/packages/tgui/components/Tooltip.tsx +++ b/tgui/packages/tgui/components/Tooltip.tsx @@ -1,9 +1,12 @@ +/* eslint-disable react/no-deprecated */ +// TODO: Rewrite as an FC, remove this lint disable import { createPopper, Placement, VirtualElement } from '@popperjs/core'; -import { Component, findDOMfromVNode, InfernoNode, render } from 'inferno'; +import { Component, ReactNode } from 'react'; +import { findDOMNode, render } from 'react-dom'; type TooltipProps = { - children?: InfernoNode; - content: InfernoNode; + children?: ReactNode; + content: ReactNode; position?: Placement; }; @@ -50,14 +53,16 @@ export class Tooltip extends Component { getDOMNode() { // HACK: We don't want to create a wrapper, as it could break the layout - // of consumers, so we do the inferno equivalent of `findDOMNode(this)`. - // My attempt to avoid this was a render prop that passed in - // callbacks to onmouseenter and onmouseleave, but this was unwiedly - // to consumers, specifically buttons. - // This code is copied from `findDOMNode` in inferno-extras. + // of consumers, so we use findDOMNode. + // This is usually bad as refs are usually better, but refs did + // not work in this case, as they weren't propagating correctly. + // A previous attempt was made as a render prop that passed an ID, + // but this made consuming use too unwieldly. // Because this component is written in TypeScript, we will know // immediately if this internal variable is removed. - return findDOMfromVNode(this.$LI, true); + // + // eslint-disable-next-line react/no-find-dom-node + return findDOMNode(this) as Element; } componentDidMount() { @@ -103,33 +108,28 @@ export class Tooltip extends Component { return; } - render( - {this.props.content}, - renderedTooltip, - () => { - let singletonPopper = Tooltip.singletonPopper; - if (singletonPopper === undefined) { - singletonPopper = createPopper( - Tooltip.virtualElement, - renderedTooltip!, - { - ...DEFAULT_OPTIONS, - placement: this.props.position || 'auto', - } - ); - - Tooltip.singletonPopper = singletonPopper; - } else { - singletonPopper.setOptions({ + render({this.props.content}, renderedTooltip, () => { + let singletonPopper = Tooltip.singletonPopper; + if (singletonPopper === undefined) { + singletonPopper = createPopper( + Tooltip.virtualElement, + renderedTooltip!, + { ...DEFAULT_OPTIONS, placement: this.props.position || 'auto', - }); + }, + ); - singletonPopper.update(); - } - }, - this.context - ); + Tooltip.singletonPopper = singletonPopper; + } else { + singletonPopper.setOptions({ + ...DEFAULT_OPTIONS, + placement: this.props.position || 'auto', + }); + + singletonPopper.update(); + } + }); } componentDidUpdate() { diff --git a/tgui/packages/tgui/components/TrackOutsideClicks.tsx b/tgui/packages/tgui/components/TrackOutsideClicks.tsx index 578b4f55db..c8451fe12d 100644 --- a/tgui/packages/tgui/components/TrackOutsideClicks.tsx +++ b/tgui/packages/tgui/components/TrackOutsideClicks.tsx @@ -1,14 +1,14 @@ -import { Component, createRef } from 'inferno'; +import { Component, createRef, PropsWithChildren } from 'react'; type Props = { onOutsideClick: () => void; -}; +} & PropsWithChildren; export class TrackOutsideClicks extends Component { ref = createRef(); - constructor() { - super(); + constructor(props) { + super(props); this.handleOutsideClick = this.handleOutsideClick.bind(this); diff --git a/tgui/packages/tgui/components/VirtualList.tsx b/tgui/packages/tgui/components/VirtualList.tsx new file mode 100644 index 0000000000..263d343bb2 --- /dev/null +++ b/tgui/packages/tgui/components/VirtualList.tsx @@ -0,0 +1,69 @@ +import { + PropsWithChildren, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; + +/** + * A vertical list that renders items to fill space up to the extents of the + * current window, and then defers rendering of other items until they come + * into view. + */ +export const VirtualList = (props: PropsWithChildren) => { + const { children } = props; + const containerRef = useRef(null as HTMLDivElement | null); + const [visibleElements, setVisibleElements] = useState(1); + const [padding, setPadding] = useState(0); + + const adjustExtents = useCallback(() => { + const { current } = containerRef; + + if ( + !children || + !Array.isArray(children) || + !current || + visibleElements >= children.length + ) { + return; + } + + const unusedArea = + document.body.offsetHeight - current.getBoundingClientRect().bottom; + + const averageItemHeight = Math.ceil(current.offsetHeight / visibleElements); + + if (unusedArea > 0) { + const newVisibleElements = Math.min( + children.length, + visibleElements + + Math.max(1, Math.ceil(unusedArea / averageItemHeight)), + ); + + setVisibleElements(newVisibleElements); + + setPadding((children.length - newVisibleElements) * averageItemHeight); + } + }, [containerRef, visibleElements, setVisibleElements, setPadding]); + + useEffect(() => { + adjustExtents(); + + const interval = setInterval(adjustExtents, 100); + + return () => clearInterval(interval); + }, [adjustExtents]); + + return ( +
+
+ {Array.isArray(children) ? children.slice(0, visibleElements) : null} +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/components/index.ts b/tgui/packages/tgui/components/index.ts index 6e117bf838..03efe65a43 100644 --- a/tgui/packages/tgui/components/index.ts +++ b/tgui/packages/tgui/components/index.ts @@ -14,14 +14,16 @@ export { ByondUi } from './ByondUi'; export { Chart } from './Chart'; export { Collapsible } from './Collapsible'; export { ColorBox } from './ColorBox'; +export { Dialog } from './Dialog'; export { Dimmer } from './Dimmer'; export { Divider } from './Divider'; export { DraggableControl } from './DraggableControl'; export { Dropdown } from './Dropdown'; -export { Flex } from './Flex'; export { FitText } from './FitText'; +export { Flex } from './Flex'; export { Grid } from './Grid'; export { Icon } from './Icon'; +export { Image } from './Image'; export { InfinitePlane } from './InfinitePlane'; export { Input } from './Input'; export { KeyListener } from './KeyListener'; @@ -33,18 +35,18 @@ export { Modal } from './Modal'; export { NanoMap } from './NanoMap'; export { NoticeBox } from './NoticeBox'; export { NumberInput } from './NumberInput'; -export { ProgressBar } from './ProgressBar'; export { Popper } from './Popper'; +export { ProgressBar } from './ProgressBar'; export { RestrictedInput } from './RestrictedInput'; export { RoundGauge } from './RoundGauge'; export { Section } from './Section'; export { Slider } from './Slider'; -export { StyleableSection } from './StyleableSection'; export { Stack } from './Stack'; +export { StyleableSection } from './StyleableSection'; export { Table } from './Table'; export { Tabs } from './Tabs'; export { TextArea } from './TextArea'; export { TimeDisplay } from './TimeDisplay'; -export { TrackOutsideClicks } from './TrackOutsideClicks'; export { Tooltip } from './Tooltip'; -export { Dialog } from './Dialog'; +export { TrackOutsideClicks } from './TrackOutsideClicks'; +export { VirtualList } from './VirtualList'; diff --git a/tgui/packages/tgui/constants.ts b/tgui/packages/tgui/constants.ts index f4cb574dbb..909f804179 100644 --- a/tgui/packages/tgui/constants.ts +++ b/tgui/packages/tgui/constants.ts @@ -91,94 +91,94 @@ export const CSS_COLORS = [ // go use /client/verb/generate_tgui_radio_constants() in communications.dm. export const RADIO_CHANNELS = [ { - 'name': 'Mercenary', - 'freq': 1213, - 'color': '#6D3F40', + name: 'Mercenary', + freq: 1213, + color: '#6D3F40', }, { - 'name': 'Raider', - 'freq': 1277, - 'color': '#6D3F40', + name: 'Raider', + freq: 1277, + color: '#6D3F40', }, { - 'name': 'Special Ops', - 'freq': 1341, - 'color': '#5C5C8A', + name: 'Special Ops', + freq: 1341, + color: '#5C5C8A', }, { - 'name': 'AI Private', - 'freq': 1343, - 'color': '#FF00FF', + name: 'AI Private', + freq: 1343, + color: '#FF00FF', }, { - 'name': 'Response Team', - 'freq': 1345, - 'color': '#5C5C8A', + name: 'Response Team', + freq: 1345, + color: '#5C5C8A', }, { - 'name': 'Supply', - 'freq': 1347, - 'color': '#5F4519', + name: 'Supply', + freq: 1347, + color: '#5F4519', }, { - 'name': 'Service', - 'freq': 1349, - 'color': '#6eaa2c', + name: 'Service', + freq: 1349, + color: '#6eaa2c', }, { - 'name': 'Science', - 'freq': 1351, - 'color': '#993399', + name: 'Science', + freq: 1351, + color: '#993399', }, { - 'name': 'Command', - 'freq': 1353, - 'color': '#193A7A', + name: 'Command', + freq: 1353, + color: '#193A7A', }, { - 'name': 'Medical', - 'freq': 1355, - 'color': '#008160', + name: 'Medical', + freq: 1355, + color: '#008160', }, { - 'name': 'Engineering', - 'freq': 1357, - 'color': '#A66300', + name: 'Engineering', + freq: 1357, + color: '#A66300', }, { - 'name': 'Security', - 'freq': 1359, - 'color': '#A30000', + name: 'Security', + freq: 1359, + color: '#A30000', }, { - 'name': 'Explorer', - 'freq': 1361, - 'color': '#555555', + name: 'Explorer', + freq: 1361, + color: '#555555', }, { - 'name': 'Talon', - 'freq': 1363, - 'color': '#555555', + name: 'Talon', + freq: 1363, + color: '#555555', }, { - 'name': 'Common', - 'freq': 1459, - 'color': '#008000', + name: 'Common', + freq: 1459, + color: '#008000', }, { - 'name': 'Entertainment', - 'freq': 1461, - 'color': '#339966', + name: 'Entertainment', + freq: 1461, + color: '#339966', }, { - 'name': 'Security(I)', - 'freq': 1475, - 'color': '#008000', + name: 'Security(I)', + freq: 1475, + color: '#008000', }, { - 'name': 'Medical(I)', - 'freq': 1485, - 'color': '#008000', + name: 'Medical(I)', + freq: 1485, + color: '#008000', }, ] as const; @@ -187,58 +187,58 @@ Entries must match /code/defines/gases.dm entries. */ const GASES = [ { - 'id': 'oxygen', - 'name': 'Oxygen', - 'label': 'O₂', - 'color': 'blue', + id: 'oxygen', + name: 'Oxygen', + label: 'O₂', + color: 'blue', }, { - 'id': 'nitrogen', - 'name': 'Nitrogen', - 'label': 'N₂', - 'color': 'green', + id: 'nitrogen', + name: 'Nitrogen', + label: 'N₂', + color: 'green', }, { - 'id': 'carbon_dioxide', - 'name': 'Carbon Dioxide', - 'label': 'CO₂', - 'color': 'grey', + id: 'carbon_dioxide', + name: 'Carbon Dioxide', + label: 'CO₂', + color: 'grey', }, { - 'id': 'phoron', - 'name': 'Phoron', - 'label': 'Phoron', - 'color': 'pink', + id: 'phoron', + name: 'Phoron', + label: 'Phoron', + color: 'pink', }, { - 'id': 'volatile_fuel', - 'name': 'Volatile Fuel', - 'label': 'EXP', - 'color': 'teal', + id: 'volatile_fuel', + name: 'Volatile Fuel', + label: 'EXP', + color: 'teal', }, { - 'id': 'nitrous_oxide', - 'name': 'Nitrous Oxide', - 'label': 'N₂O', - 'color': 'red', + id: 'nitrous_oxide', + name: 'Nitrous Oxide', + label: 'N₂O', + color: 'red', }, { - 'id': 'other', - 'name': 'Other', - 'label': 'Other', - 'color': 'white', + id: 'other', + name: 'Other', + label: 'Other', + color: 'white', }, { - 'id': 'pressure', - 'name': 'Pressure', - 'label': 'Pressure', - 'color': 'average', + id: 'pressure', + name: 'Pressure', + label: 'Pressure', + color: 'average', }, { - 'id': 'temperature', - 'name': 'Temperature', - 'label': 'Temperature', - 'color': 'yellow', + id: 'temperature', + name: 'Temperature', + label: 'Temperature', + color: 'yellow', }, ] as const; @@ -252,7 +252,7 @@ export const getGasLabel = (gasId: string, fallbackValue?: string) => { const gasSearchId = gasId.toLowerCase(); const gasSearchName = gasId.replace(/(^\w{1})|(\s+\w{1})/g, (letter) => - letter.toUpperCase() + letter.toUpperCase(), ); for (let idx = 0; idx < GASES.length; idx++) { @@ -272,7 +272,7 @@ export const getGasColor = (gasId: string) => { const gasSearchId = gasId.toLowerCase(); const gasSearchName = gasId.replace(/(^\w{1})|(\s+\w{1})/g, (letter) => - letter.toUpperCase() + letter.toUpperCase(), ); for (let idx = 0; idx < GASES.length; idx++) { @@ -292,7 +292,7 @@ export const getGasFromId = (gasId: string): Gas | undefined => { const gasSearchId = gasId.toLowerCase(); const gasSearchName = gasId.replace(/(^\w{1})|(\s+\w{1})/g, (letter) => - letter.toUpperCase() + letter.toUpperCase(), ); for (let idx = 0; idx < GASES.length; idx++) { diff --git a/tgui/packages/tgui/debug/KitchenSink.jsx b/tgui/packages/tgui/debug/KitchenSink.jsx index cd6ba33c42..428d440097 100644 --- a/tgui/packages/tgui/debug/KitchenSink.jsx +++ b/tgui/packages/tgui/debug/KitchenSink.jsx @@ -4,6 +4,8 @@ * @license MIT */ +import { useState } from 'react'; + import { useLocalState } from '../backend'; import { Flex, Section, Tabs } from '../components'; import { Pane, Window } from '../layouts'; @@ -23,7 +25,7 @@ const getStories = () => r.keys().map((path) => r(path)); export const KitchenSink = (props) => { const { panel } = props; const [theme] = useLocalState('kitchenSinkTheme'); - const [pageIndex, setPageIndex] = useLocalState('pageIndex', 0); + const [pageIndex, setPageIndex] = useState(0); const stories = getStories(); const story = stories[pageIndex]; const Layout = panel ? Pane : Window; @@ -38,7 +40,8 @@ export const KitchenSink = (props) => { key={i} color="transparent" selected={i === pageIndex} - onClick={() => setPageIndex(i)}> + onClick={() => setPageIndex(i)} + > {story.meta.title} ))} diff --git a/tgui/packages/tgui/debug/hooks.js b/tgui/packages/tgui/debug/hooks.js index d324dd68d8..fc4901c496 100644 --- a/tgui/packages/tgui/debug/hooks.js +++ b/tgui/packages/tgui/debug/hooks.js @@ -4,7 +4,7 @@ * @license MIT */ -import { useSelector } from 'common/redux'; +import { useSelector } from '../backend'; import { selectDebug } from './selectors'; -export const useDebug = (context) => useSelector(context, selectDebug); +export const useDebug = () => useSelector(selectDebug); diff --git a/tgui/packages/tgui/debug/middleware.js b/tgui/packages/tgui/debug/middleware.js index 75687f2541..3f7414335e 100644 --- a/tgui/packages/tgui/debug/middleware.js +++ b/tgui/packages/tgui/debug/middleware.js @@ -5,9 +5,14 @@ */ import { KEY_BACKSPACE, KEY_F10, KEY_F11, KEY_F12 } from 'common/keycodes'; + import { globalEvents } from '../events'; import { acquireHotKey } from '../hotkeys'; -import { openExternalBrowser, toggleDebugLayout, toggleKitchenSink } from './actions'; +import { + openExternalBrowser, + toggleDebugLayout, + toggleKitchenSink, +} from './actions'; // prettier-ignore const relayedTypes = [ diff --git a/tgui/packages/tgui/drag.ts b/tgui/packages/tgui/drag.ts index 4b6f82e120..584666a97a 100644 --- a/tgui/packages/tgui/drag.ts +++ b/tgui/packages/tgui/drag.ts @@ -4,10 +4,10 @@ * @license MIT */ +import { storage } from 'common/storage'; import { vecAdd, vecMultiply, vecScale, vecSubtract } from 'common/vector'; import { createLogger } from './logging'; -import { storage } from 'common/storage'; const logger = createLogger('drag'); const pixelRatio = window.devicePixelRatio ?? 1; @@ -76,7 +76,7 @@ const getScreenSize = (): [number, number] => [ export const touchRecents = ( recents: string[], touchedItem: string, - limit = 50 + limit = 50, ): [string[], string | undefined] => { const nextRecents: string[] = [touchedItem]; let trimmedItem: string | undefined; @@ -105,7 +105,7 @@ const storeWindowGeometry = async () => { // Update the list of stored geometries const [geometries, trimmedKey] = touchRecents( (await storage.get('geometries')) || [], - windowKey + windowKey, ); if (trimmedKey) { storage.remove(trimmedKey); @@ -120,7 +120,7 @@ export const recallWindowGeometry = async ( pos?: [number, number]; size?: [number, number]; locked?: boolean; - } = {} + } = {}, ) => { const geometry = options.fancy && (await storage.get(windowKey)); if (geometry) { @@ -157,7 +157,7 @@ export const recallWindowGeometry = async ( pos = vecAdd( vecScale(areaAvailable, 0.5), vecScale(size, -0.5), - vecScale(screenOffset, -1.0) + vecScale(screenOffset, -1.0), ); setWindowPosition(pos); } @@ -182,7 +182,7 @@ export const setupDrag = async () => { */ const constraintPosition = ( pos: [number, number], - size: [number, number] + size: [number, number], ): [boolean, [number, number]] => { const screenPos = getScreenPosition(); const screenSize = getScreenSize(); @@ -203,12 +203,12 @@ const constraintPosition = ( }; // Start dragging the window -export const dragStartHandler = (event: MouseEvent) => { +export const dragStartHandler = (event) => { logger.log('drag start'); dragging = true; dragPointOffset = vecSubtract( [event.screenX, event.screenY], - getWindowPosition() + getWindowPosition(), ); // Focus click target (event.target as HTMLElement)?.focus(); @@ -218,7 +218,7 @@ export const dragStartHandler = (event: MouseEvent) => { }; // End dragging the window -const dragEndHandler = (event: MouseEvent) => { +const dragEndHandler = (event) => { logger.log('drag end'); dragMoveHandler(event); document.removeEventListener('mousemove', dragMoveHandler); @@ -234,7 +234,7 @@ const dragMoveHandler = (event: MouseEvent) => { } event.preventDefault(); setWindowPosition( - vecSubtract([event.screenX, event.screenY], dragPointOffset) + vecSubtract([event.screenX, event.screenY], dragPointOffset), ); }; @@ -246,7 +246,7 @@ export const resizeStartHandler = resizing = true; dragPointOffset = vecSubtract( [event.screenX, event.screenY], - getWindowPosition() + getWindowPosition(), ); initialSize = getWindowSize(); // Focus click target @@ -274,7 +274,7 @@ const resizeMoveHandler = (event: MouseEvent) => { event.preventDefault(); const currentOffset = vecSubtract( [event.screenX, event.screenY], - getWindowPosition() + getWindowPosition(), ); const delta = vecSubtract(currentOffset, dragPointOffset); // Extra 1x1 area is added to ensure the browser can see the cursor diff --git a/tgui/packages/tgui/events.test.ts b/tgui/packages/tgui/events.test.ts index 8f3e8e3109..e7a6c25018 100644 --- a/tgui/packages/tgui/events.test.ts +++ b/tgui/packages/tgui/events.test.ts @@ -1,4 +1,10 @@ -import { KeyEvent, addScrollableNode, canStealFocus, removeScrollableNode, setupGlobalEvents } from './events'; +import { + addScrollableNode, + canStealFocus, + KeyEvent, + removeScrollableNode, + setupGlobalEvents, +} from './events'; describe('focusEvents', () => { afterEach(() => { diff --git a/tgui/packages/tgui/events.ts b/tgui/packages/tgui/events.ts index 670f03e808..cc53d31bfa 100644 --- a/tgui/packages/tgui/events.ts +++ b/tgui/packages/tgui/events.ts @@ -6,15 +6,14 @@ * @license MIT */ -import { KEY_ALT, KEY_CTRL, KEY_F1, KEY_F12, KEY_SHIFT } from 'common/keycodes'; - import { EventEmitter } from 'common/events'; +import { KEY_ALT, KEY_CTRL, KEY_F1, KEY_F12, KEY_SHIFT } from 'common/keycodes'; export const globalEvents = new EventEmitter(); let ignoreWindowFocus = false; export const setupGlobalEvents = ( - options: { ignoreWindowFocus?: boolean } = {} + options: { ignoreWindowFocus?: boolean } = {}, ): void => { ignoreWindowFocus = !!options.ignoreWindowFocus; }; @@ -123,7 +122,6 @@ window.addEventListener('focusin', (e) => { setWindowFocus(true); if (canStealFocus(e.target as HTMLElement)) { stealFocus(e.target as HTMLElement); - return; } }); diff --git a/tgui/packages/tgui/format.test.ts b/tgui/packages/tgui/format.test.ts index 011d9e9e6c..8fdc6683c4 100644 --- a/tgui/packages/tgui/format.test.ts +++ b/tgui/packages/tgui/format.test.ts @@ -1,4 +1,10 @@ -import { formatDb, formatMoney, formatSiBaseTenUnit, formatSiUnit, formatTime } from './format'; +import { + formatDb, + formatMoney, + formatSiBaseTenUnit, + formatSiUnit, + formatTime, +} from './format'; describe('formatSiUnit', () => { it('formats base values correctly', () => { diff --git a/tgui/packages/tgui/format.ts b/tgui/packages/tgui/format.ts index eb76bd2255..4ebc03e710 100644 --- a/tgui/packages/tgui/format.ts +++ b/tgui/packages/tgui/format.ts @@ -36,7 +36,7 @@ const SI_BASE_INDEX = SI_SYMBOLS.indexOf(' '); export const formatSiUnit = ( value: number, minBase1000 = -SI_BASE_INDEX, - unit = '' + unit = '', ): string => { if (!isFinite(value)) { return value.toString(); @@ -123,7 +123,7 @@ const SI_BASE_TEN_UNITS = [ export const formatSiBaseTenUnit = ( value: number, minBase1000 = 0, - unit = '' + unit = '', ): string => { if (!isFinite(value)) { return 'NaN'; @@ -147,7 +147,7 @@ export const formatSiBaseTenUnit = ( */ export const formatTime = ( val: number, - formatType: 'short' | 'default' = 'default' + formatType: 'short' | 'default' = 'default', ): string => { const totalSeconds = Math.floor(val / 10); const hours = Math.floor(totalSeconds / 3600); diff --git a/tgui/packages/tgui/hotkeys.ts b/tgui/packages/tgui/hotkeys.ts index 6e945cf4eb..426beb6d40 100644 --- a/tgui/packages/tgui/hotkeys.ts +++ b/tgui/packages/tgui/hotkeys.ts @@ -5,6 +5,7 @@ */ import * as keycodes from 'common/keycodes'; + import { globalEvents, KeyEvent } from './events'; import { createLogger } from './logging'; diff --git a/tgui/packages/tgui/http.ts b/tgui/packages/tgui/http.ts index a0ea97c3b6..b9dfe9c60c 100644 --- a/tgui/packages/tgui/http.ts +++ b/tgui/packages/tgui/http.ts @@ -4,7 +4,7 @@ export const fetchRetry = ( url: string, options?: RequestInit, - retryTimer: number = 1000 + retryTimer: number = 1000, ): Promise => { return fetch(url, options).catch(() => { return new Promise((resolve) => { diff --git a/tgui/packages/tgui/index.tsx b/tgui/packages/tgui/index.tsx index b1e6635d35..9f97a52d67 100644 --- a/tgui/packages/tgui/index.tsx +++ b/tgui/packages/tgui/index.tsx @@ -19,15 +19,15 @@ import './styles/themes/syndicate.scss'; import './styles/themes/wizard.scss'; import './styles/themes/abstract.scss'; -import { configureStore } from './store'; - -import { captureExternalLinks } from './links'; -import { createRenderer } from './renderer'; import { perf } from 'common/perf'; +import { setupHotReloading } from 'tgui-dev-server/link/client.cjs'; + +import { setGlobalStore } from './backend'; import { setupGlobalEvents } from './events'; import { setupHotKeys } from './hotkeys'; -import { setupHotReloading } from 'tgui-dev-server/link/client.cjs'; -import { setGlobalStore } from './backend'; +import { captureExternalLinks } from './links'; +import { createRenderer } from './renderer'; +import { configureStore } from './store'; perf.mark('inception', window.performance?.timing?.navigationStart); perf.mark('init'); diff --git a/tgui/packages/tgui/interfaces/AICard.jsx b/tgui/packages/tgui/interfaces/AICard.jsx index a2b9105812..625e2c7db8 100644 --- a/tgui/packages/tgui/interfaces/AICard.jsx +++ b/tgui/packages/tgui/interfaces/AICard.jsx @@ -1,5 +1,5 @@ import { useBackend } from '../backend'; -import { Button, ProgressBar, LabeledList, Box, Section } from '../components'; +import { Box, Button, LabeledList, ProgressBar, Section } from '../components'; import { Window } from '../layouts'; export const AICard = (props) => { @@ -18,7 +18,7 @@ export const AICard = (props) => { if (has_ai === 0) { return ( - +
@@ -49,7 +49,7 @@ export const AICard = (props) => { } return ( - +
diff --git a/tgui/packages/tgui/interfaces/APC.jsx b/tgui/packages/tgui/interfaces/APC.jsx index 29acd63819..2a533d07cb 100644 --- a/tgui/packages/tgui/interfaces/APC.jsx +++ b/tgui/packages/tgui/interfaces/APC.jsx @@ -1,9 +1,16 @@ -import { Fragment } from 'inferno'; import { useBackend } from '../backend'; -import { Box, Button, Dimmer, Icon, LabeledList, ProgressBar, Section } from '../components'; +import { + Box, + Button, + Dimmer, + Icon, + LabeledList, + ProgressBar, + Section, +} from '../components'; import { Window } from '../layouts'; -import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; import { FullscreenNotice } from './common/FullscreenNotice'; +import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; export const APC = (props) => { const { act, data } = useBackend(); @@ -17,7 +24,7 @@ export const APC = (props) => { } return ( - + {body} ); @@ -77,16 +84,16 @@ const ApcContent = (props) => { const adjustedCellChange = data.powerCellStatus / 100; return ( - + <> + <> Fault in ID authenticator. Please contact maintenance for service. - + } />
@@ -103,7 +110,8 @@ const ApcContent = (props) => { disabled={locked} onClick={() => act('breaker')} /> - }> + } + > [ {externalPowerStatus.externalPowerText} ] @@ -120,7 +128,8 @@ const ApcContent = (props) => { disabled={locked} onClick={() => act('charge')} /> - }> + } + > [ {chargingStatus.chargingText} ] @@ -134,11 +143,12 @@ const ApcContent = (props) => { key={channel.title} label={channel.title} buttons={ - + <> = 2 ? 'good' : 'bad'}> + color={channel.status >= 2 ? 'good' : 'bad'} + > {channel.status >= 2 ? 'On' : 'Off'}
- + ); }; diff --git a/tgui/packages/tgui/interfaces/AccountsTerminal.jsx b/tgui/packages/tgui/interfaces/AccountsTerminal.jsx index cd24a0aa73..b00126bf54 100644 --- a/tgui/packages/tgui/interfaces/AccountsTerminal.jsx +++ b/tgui/packages/tgui/interfaces/AccountsTerminal.jsx @@ -1,5 +1,13 @@ import { useBackend, useSharedState } from '../backend'; -import { Box, Button, LabeledList, Input, Section, Table, Tabs } from '../components'; +import { + Box, + Button, + Input, + LabeledList, + Section, + Table, + Tabs, +} from '../components'; import { Window } from '../layouts'; export const AccountsTerminal = (props) => { @@ -42,19 +50,22 @@ const AccountTerminalContent = (props) => { act('view_accounts_list')}> + onClick={() => act('view_accounts_list')} + > Home act('create_account')}> + onClick={() => act('create_account')} + > New Account act('print')}> + onClick={() => act('print')} + > Print @@ -121,7 +132,8 @@ const DetailedView = (props) => { content="Suspend" onClick={() => act('toggle_suspension')} /> - }> + } + > #{account_number} @@ -201,13 +213,14 @@ const ListView = (props) => { + key={acc.account_index} + > @@ -44,13 +48,15 @@ export const BombTester = (props) => { {(tank1 && ( )) || ( )} @@ -59,13 +65,15 @@ export const BombTester = (props) => { {(tank2 && ( )) || ( )} @@ -76,7 +84,8 @@ export const BombTester = (props) => { - }> + } + > {(canister && {canister}) || ( No tank connected. )} @@ -100,7 +109,8 @@ export const BombTester = (props) => { icon="bomb" fontSize={2} onClick={() => act('start_sim')} - fluid> + fluid + > Begin Simulation
@@ -177,15 +187,16 @@ class BombTesterSimulation extends Component { const newStyle = { position: 'relative', - 'left': x + 'px', - 'top': y + 'px', + left: x + 'px', + top: y + 'px', }; return (
+ style={{ overflow: 'hidden', width: '100%', height: '100%' }} + >
diff --git a/tgui/packages/tgui/interfaces/BotanyEditor.jsx b/tgui/packages/tgui/interfaces/BotanyEditor.jsx index 89ce6f8741..5210e31dd7 100644 --- a/tgui/packages/tgui/interfaces/BotanyEditor.jsx +++ b/tgui/packages/tgui/interfaces/BotanyEditor.jsx @@ -1,5 +1,5 @@ import { useBackend } from '../backend'; -import { Box, Button, LabeledList, Section, NoticeBox } from '../components'; +import { Box, Button, LabeledList, NoticeBox, Section } from '../components'; import { Window } from '../layouts'; export const BotanyEditor = (props) => { @@ -9,7 +9,7 @@ export const BotanyEditor = (props) => { if (activity) { return ( - + Scanning... @@ -18,7 +18,7 @@ export const BotanyEditor = (props) => { } return ( - +
{(disk && ( diff --git a/tgui/packages/tgui/interfaces/BotanyIsolator.jsx b/tgui/packages/tgui/interfaces/BotanyIsolator.jsx index e1020b6153..b46e434268 100644 --- a/tgui/packages/tgui/interfaces/BotanyIsolator.jsx +++ b/tgui/packages/tgui/interfaces/BotanyIsolator.jsx @@ -1,5 +1,5 @@ import { useBackend } from '../backend'; -import { Box, Button, LabeledList, Section, NoticeBox } from '../components'; +import { Box, Button, LabeledList, NoticeBox, Section } from '../components'; import { Window } from '../layouts'; export const BotanyIsolator = (props) => { @@ -17,7 +17,7 @@ export const BotanyIsolator = (props) => { if (activity) { return ( - + Scanning... @@ -26,7 +26,7 @@ export const BotanyIsolator = (props) => { } return ( - +
{(hasGenetics && ( @@ -43,7 +43,8 @@ export const BotanyIsolator = (props) => { diff --git a/tgui/packages/tgui/interfaces/BrigTimer.jsx b/tgui/packages/tgui/interfaces/BrigTimer.jsx index 7ef7910cf9..0e2d1fd7ef 100644 --- a/tgui/packages/tgui/interfaces/BrigTimer.jsx +++ b/tgui/packages/tgui/interfaces/BrigTimer.jsx @@ -1,19 +1,19 @@ import { round } from 'common/math'; -import { Fragment } from 'inferno'; + import { useBackend } from '../backend'; -import { Button, Section, NumberInput, Flex } from '../components'; -import { Window } from '../layouts'; +import { Button, Flex, NumberInput, Section } from '../components'; import { formatTime } from '../format'; +import { Window } from '../layouts'; export const BrigTimer = (props) => { const { act, data } = useBackend(); return ( - +
+ <>
))} diff --git a/tgui/packages/tgui/interfaces/Canister.jsx b/tgui/packages/tgui/interfaces/Canister.jsx index 848b34ec2b..ee90f67841 100644 --- a/tgui/packages/tgui/interfaces/Canister.jsx +++ b/tgui/packages/tgui/interfaces/Canister.jsx @@ -1,6 +1,17 @@ import { toFixed } from 'common/math'; + import { useBackend } from '../backend'; -import { AnimatedNumber, Box, Button, Icon, Knob, LabeledControls, LabeledList, Section, Tooltip } from '../components'; +import { + AnimatedNumber, + Box, + Button, + Icon, + Knob, + LabeledControls, + LabeledList, + Section, + Tooltip, +} from '../components'; import { formatSiUnit } from '../format'; import { Window } from '../layouts'; @@ -18,7 +29,7 @@ export const Canister = (props) => { holding, } = data; return ( - +
{ content="Relabel" onClick={() => act('relabel')} /> - }> + } + > { onClick={() => act('eject')} /> ) - }> + } + > {!!holding && ( {holding.name} diff --git a/tgui/packages/tgui/interfaces/Canvas.jsx b/tgui/packages/tgui/interfaces/Canvas.jsx index e181ce3d31..02b2ff9ecf 100644 --- a/tgui/packages/tgui/interfaces/Canvas.jsx +++ b/tgui/packages/tgui/interfaces/Canvas.jsx @@ -1,4 +1,5 @@ -import { Component, createRef } from 'inferno'; +import { Component, createRef } from 'react'; + import { useBackend } from '../backend'; import { Box, Button } from '../components'; import { Window } from '../layouts'; @@ -65,7 +66,8 @@ class PaintCanvas extends Component { width={width * dotsize || 300} height={height * dotsize || 300} {...rest} - onClick={(e) => this.clickwrapper(e)}> + onClick={(e) => this.clickwrapper(e)} + > Canvas failed to render. ); @@ -85,7 +87,8 @@ export const Canvas = (props) => { return ( + height={Math.min(700, height * dotsize + 72)} + > a - b, + Alphabetical: (a, b) => a - b, 'By availability': (a, b) => -(a.affordable - b.affordable), 'By price': (a, b) => a.price - b.price, }; export const CasinoPrizeDispenser = () => { return ( - + - + <> - + ); @@ -123,7 +131,8 @@ const CasinoPrizeDispenserItemsCategory = (properties) => { lineHeight="20px" style={{ float: 'left', - }}> + }} + > {item.name}
- - + + )}
@@ -102,42 +105,41 @@ export const CharacterDirectory = (props) => { }; const ViewCharacter = (props) => { - const [overlay, setOverlay] = useLocalState('overlay', null); - return (
setOverlay(null)} + onClick={() => props.onOverlay(null)} /> - }> + } + >
- {overlay.species} + {props.overlay.species}
- - {overlay.tag} + + {props.overlay.tag}
- {overlay.erptag} + {props.overlay.erptag}
- {overlay.character_ad || 'Unset.'} + {props.overlay.character_ad || 'Unset.'}
- {overlay.ooc_notes || 'Unset.'} + {props.overlay.ooc_notes || 'Unset.'}
- {overlay.flavor_text || 'Unset.'} + {props.overlay.flavor_text || 'Unset.'}
@@ -149,16 +151,16 @@ const CharacterDirectoryList = (props) => { const { directory } = data; - const [sortId, _setSortId] = useLocalState('sortId', 'name'); - const [sortOrder, _setSortOrder] = useLocalState('sortOrder', 'name'); - const [overlay, setOverlay] = useLocalState('overlay', null); + const [sortId, _setSortId] = useState('name'); + const [sortOrder, _setSortOrder] = useState('name'); return (
act('refresh')} /> - }> + } + > Name @@ -182,7 +184,7 @@ const CharacterDirectoryList = (props) => { {character.erptag} @@ -377,17 +383,15 @@ const ChemMasterProductionChemical = (props) => {
@@ -399,7 +403,7 @@ const ChemMasterProductionChemical = (props) => { const ChemMasterProductionCondiment = (props) => { const { act } = useBackend(); return ( - + <> - }> + } + > + color={open ? 'bad' : 'good'} + > {open ? 'Open' : 'Closed'} + color={locked ? 'good' : 'bad'} + > {locked ? 'Locked' : 'Unlocked'} @@ -52,7 +55,8 @@ export const Cleanbot = (props) => { fluid icon={blood ? 'toggle-on' : 'toggle-off'} selected={blood} - onClick={() => act('blood')}> + onClick={() => act('blood')} + > {blood ? 'Clean' : 'Ignore'} @@ -61,7 +65,8 @@ export const Cleanbot = (props) => { fluid icon={vocal ? 'toggle-on' : 'toggle-off'} selected={vocal} - onClick={() => act('vocal')}> + onClick={() => act('vocal')} + > {vocal ? 'On' : 'Off'} @@ -111,7 +116,8 @@ export const Cleanbot = (props) => { fluid selected={wet_floors} onClick={() => act('wet_floors')} - icon="screwdriver"> + icon="screwdriver" + > {wet_floors ? 'Yes' : 'No'} @@ -121,7 +127,8 @@ export const Cleanbot = (props) => { color="brown" selected={spray_blood} onClick={() => act('spray_blood')} - icon="screwdriver"> + icon="screwdriver" + > {spray_blood ? 'Yes' : 'No'} diff --git a/tgui/packages/tgui/interfaces/CloningConsole.jsx b/tgui/packages/tgui/interfaces/CloningConsole.jsx index cde891c562..3045d257f1 100644 --- a/tgui/packages/tgui/interfaces/CloningConsole.jsx +++ b/tgui/packages/tgui/interfaces/CloningConsole.jsx @@ -1,9 +1,22 @@ import { round } from 'common/math'; -import { Fragment } from 'inferno'; + import { useBackend } from '../backend'; -import { Box, Button, Flex, Icon, LabeledList, NoticeBox, ProgressBar, Section, Tabs } from '../components'; +import { + Box, + Button, + Flex, + Icon, + LabeledList, + NoticeBox, + ProgressBar, + Section, + Tabs, +} from '../components'; import { COLORS } from '../constants'; -import { ComplexModal, modalRegisterBodyOverride } from '../interfaces/common/ComplexModal'; +import { + ComplexModal, + modalRegisterBodyOverride, +} from '../interfaces/common/ComplexModal'; import { Window } from '../layouts'; const viewRecordModalBodyOverride = (modal) => { @@ -17,7 +30,7 @@ const viewRecordModalBodyOverride = (modal) => { {realname} {damages.length > 1 ? ( - + <> {damages[0]} @@ -33,7 +46,7 @@ const viewRecordModalBodyOverride = (modal) => { {damages[1]} - + ) : ( Unknown )} @@ -116,7 +129,7 @@ export const CloningConsole = (props) => { const { menu } = data; modalRegisterBodyOverride('view_rec', viewRecordModalBodyOverride); return ( - + @@ -142,7 +155,8 @@ const CloningConsoleNavigation = (props) => { act('menu', { num: 1, }) - }> + } + > Main { act('menu', { num: 2, }) - }> + } + > Records @@ -186,12 +201,12 @@ const CloningConsoleMain = (props) => { } = data; const isLocked = locked && !!occupant; return ( - + <>
+ <> Scanner Lock:  @@ -208,8 +223,9 @@ const CloningConsoleMain = (props) => { content="Eject Occupant" onClick={() => act('eject')} /> - - }> + + } + > {loading ? ( @@ -255,7 +271,8 @@ const CloningConsoleMain = (props) => { average: [0.25, 0.75], bad: [-Infinity, 0.25], }} - mt="0.5rem"> + mt="0.5rem" + > {round(pod.progress, 0) + '%'} ); @@ -287,7 +304,8 @@ const CloningConsoleMain = (props) => { width="64px" textAlign="center" display="inline-block" - mr="0.5rem"> + mr="0.5rem" + > { No pods detected. Unable to clone. )}
-
+ ); }; @@ -376,9 +394,9 @@ const CloningConsoleStatus = (props) => {
+ <> {!!autoallowed && ( - + <> Auto-processing:  @@ -392,7 +410,7 @@ const CloningConsoleStatus = (props) => { }) } /> - + )}
-
+ )) || (
No items inserted. diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx b/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx index c9aac4f626..97613ed7e5 100644 --- a/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx +++ b/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx @@ -1,11 +1,10 @@ -import { Fragment } from 'inferno'; import { useBackend } from '../backend'; -import { Button, LabeledList, Box, Section } from '../components'; +import { Box, Button, LabeledList, Section } from '../components'; import { Window } from '../layouts'; export const CommunicationsConsole = (props) => { return ( - + @@ -37,10 +36,10 @@ export const CommunicationsConsoleContent = (props) => { } return ( - + <> {mainTemplate} - + ); }; @@ -87,7 +86,7 @@ const CommunicationsConsoleMain = (props) => { }); return ( - + <>
@@ -161,7 +160,7 @@ const CommunicationsConsoleMain = (props) => {
-
+ ); }; @@ -185,7 +184,7 @@ const CommunicationsConsoleAuth = (props) => { } return ( - + <>
{(is_ai && ( @@ -231,7 +230,7 @@ const CommunicationsConsoleAuth = (props) => { )}
-
+ ); }; @@ -252,7 +251,8 @@ const CommunicationsConsoleMessage = (props) => { disabled={!authenticated} onClick={() => act('messagelist')} /> - }> + } + > {message_current.contents}
); @@ -289,7 +289,8 @@ const CommunicationsConsoleMessage = (props) => { content="Back To Main Menu" onClick={() => act('main')} /> - }> + } + > {(messages.length && messageRows) || ( @@ -326,7 +327,8 @@ const CommunicationsConsoleStatusDisplay = (props) => { content="Back To Main Menu" onClick={() => act('main')} /> - }> + } + > {presetButtons} diff --git a/tgui/packages/tgui/interfaces/Communicator.tsx b/tgui/packages/tgui/interfaces/Communicator.tsx index dd6ea28120..2ad5edff08 100644 --- a/tgui/packages/tgui/interfaces/Communicator.tsx +++ b/tgui/packages/tgui/interfaces/Communicator.tsx @@ -1,9 +1,20 @@ import { filter } from 'common/collections'; import { BooleanLike } from 'common/react'; import { decodeHtmlEntities, toTitleCase } from 'common/string'; -import { Fragment } from 'inferno'; -import { useBackend, useLocalState } from '../backend'; -import { Box, ByondUi, Button, Flex, Icon, LabeledList, Input, Section, Table } from '../components'; +import { useState } from 'react'; + +import { useBackend } from '../backend'; +import { + Box, + Button, + ByondUi, + Flex, + Icon, + Input, + LabeledList, + Section, + Table, +} from '../components'; import { Window } from '../layouts'; import { CrewManifestContent } from './CrewManifest'; @@ -51,10 +62,10 @@ export const Communicator = (props) => { * 1: Popup Video * 2: Minimized Video */ - const [videoSetting, setVideoSetting] = useLocalState('videoSetting', 0); + const [videoSetting, setVideoSetting] = useState(0); return ( - + {video_comm && ( { /> )} {(!video_comm || videoSetting !== 0) && ( - + <> + overflowY: 'auto', + }} + > {TabToTemplate[currentTab] || } - + )} @@ -139,11 +151,12 @@ const VideoComm = (props) => { return ( + position: 'absolute', + right: '5px', + bottom: '50px', + zIndex: '1', + }} + >
@@ -331,12 +344,13 @@ const HomeTab = (props) => {
@@ -882,14 +905,15 @@ const MessagingThreadTab = (props) => { + width="100%" + > {enforceLengthLimit( 'Conversation with ', decodeHtmlEntities(targetAddressName), - 30 + 30, )} } @@ -903,12 +927,14 @@ const MessagingThreadTab = (props) => { /> } height="100%" - stretchContents> + stretchContents + >
+ height: '95%', + overflowY: 'auto', + }} + > {imList.map( (im, i, filterArr) => (im.to_address === targetAddress || @@ -916,25 +942,27 @@ const MessagingThreadTab = (props) => { + key={i} + > + inline + > {decodeHtmlEntities(im.im)} - ) + ), )}
diff --git a/tgui/packages/tgui/interfaces/CookingAppliance.jsx b/tgui/packages/tgui/interfaces/CookingAppliance.jsx index 48930ec333..4efa443a6a 100644 --- a/tgui/packages/tgui/interfaces/CookingAppliance.jsx +++ b/tgui/packages/tgui/interfaces/CookingAppliance.jsx @@ -1,5 +1,12 @@ import { useBackend } from '../backend'; -import { Button, Flex, LabeledList, ProgressBar, Section, AnimatedNumber } from '../components'; +import { + AnimatedNumber, + Button, + Flex, + LabeledList, + ProgressBar, + Section, +} from '../components'; import { Window } from '../layouts'; export const CookingAppliance = (props) => { @@ -15,7 +22,7 @@ export const CookingAppliance = (props) => { } = data; return ( - +
@@ -23,7 +30,8 @@ export const CookingAppliance = (props) => { + maxValue={optimalTemp} + > °C / {optimalTemp}°C @@ -38,7 +46,7 @@ export const CookingAppliance = (props) => { {our_contents.map((content, i) => { if (content.empty) { return ( - + @@ -50,12 +58,14 @@ export const CookingAppliance = (props) => { + key={i} + > @@ -63,7 +73,8 @@ export const CookingAppliance = (props) => { + maxValue={1} + > {content.progressText[1]} diff --git a/tgui/packages/tgui/interfaces/CrewManifest.tsx b/tgui/packages/tgui/interfaces/CrewManifest.tsx index a11cbe62ff..d814f14510 100644 --- a/tgui/packages/tgui/interfaces/CrewManifest.tsx +++ b/tgui/packages/tgui/interfaces/CrewManifest.tsx @@ -1,8 +1,9 @@ +import { decodeHtmlEntities } from 'common/string'; + import { useBackend } from '../backend'; import { Box, Section, Table } from '../components'; -import { Window } from '../layouts'; import { COLORS } from '../constants'; -import { decodeHtmlEntities } from 'common/string'; +import { Window } from '../layouts'; /* * Shared by the following templates (and used individually too) @@ -50,13 +51,15 @@ export const CrewManifestContent = (props) => { backgroundColor={COLORS.manifest[cat.cat.toLowerCase()]} m={-1} pt={1} - pb={1}> + pb={1} + > {cat.cat} } - key={cat.cat}> + key={cat.cat} + >
Name @@ -72,7 +75,7 @@ export const CrewManifestContent = (props) => { ))}
- ) + ), )}
); diff --git a/tgui/packages/tgui/interfaces/CrewMonitor.jsx b/tgui/packages/tgui/interfaces/CrewMonitor.jsx index 4a53c596c0..74418f60b6 100644 --- a/tgui/packages/tgui/interfaces/CrewMonitor.jsx +++ b/tgui/packages/tgui/interfaces/CrewMonitor.jsx @@ -1,9 +1,9 @@ import { sortBy } from 'common/collections'; import { flow } from 'common/fp'; + import { useBackend, useLocalState } from '../backend'; +import { Box, Button, Icon, NanoMap, Table, Tabs } from '../components'; import { Window } from '../layouts'; -import { NanoMap, Box, Table, Button, Tabs, Icon } from '../components'; -import { Fragment } from 'inferno'; const getStatText = (cm) => { if (cm.dead) { @@ -29,7 +29,7 @@ const getStatColor = (cm) => { export const CrewMonitor = () => { return ( - + @@ -125,23 +125,25 @@ export const CrewMonitorContent = (props) => { } return ( - + <> setTabIndex(0)}> + onClick={() => setTabIndex(0)} + > Data View setTabIndex(1)}> + onClick={() => setTabIndex(1)} + > Map View {body} - + ); }; @@ -153,7 +155,7 @@ const CrewMonitorMapView = (props) => { setZoom(v)}> {data.crewmembers .filter( - (x) => x.sensor_type === 3 && ~~x.realZ === ~~config.mapZLevel + (x) => x.sensor_type === 3 && ~~x.realZ === ~~config.mapZLevel, ) .map((cm) => ( { isBeakerLoaded, } = data; return ( - + <>
{ - }> + } + > {hasOccupant ? ( @@ -71,13 +81,15 @@ const CryoContent = (props) => { min={occupant.health} max={occupant.maxHealth} value={occupant.health / occupant.maxHealth} - color={occupant.health > 0 ? 'good' : 'average'}> + color={occupant.health > 0 ? 'good' : 'average'} + > + color={statNames[occupant.stat][0]} + > {statNames[occupant.stat][1]} @@ -89,7 +101,8 @@ const CryoContent = (props) => { + ranges={{ bad: [0.01, Infinity] }} + > @@ -113,16 +126,19 @@ const CryoContent = (props) => { - }> + } + > @@ -134,7 +150,7 @@ const CryoContent = (props) => {
-
+ ); }; @@ -143,7 +159,7 @@ const CryoBeaker = (props) => { const { isBeakerLoaded, beakerLabel, beakerVolume } = data; if (isBeakerLoaded) { return ( - + <> {beakerLabel ? beakerLabel : No label} {beakerVolume ? ( @@ -155,7 +171,7 @@ const CryoBeaker = (props) => { 'Beaker is empty' )} - + ); } else { return No beaker loaded; diff --git a/tgui/packages/tgui/interfaces/CryoStorage.jsx b/tgui/packages/tgui/interfaces/CryoStorage.jsx index 10af1834cf..d947810e38 100644 --- a/tgui/packages/tgui/interfaces/CryoStorage.jsx +++ b/tgui/packages/tgui/interfaces/CryoStorage.jsx @@ -1,5 +1,7 @@ -import { useBackend, useLocalState } from '../backend'; -import { Box, Button, Section, Tabs, NoticeBox } from '../components'; +import { useState } from 'react'; + +import { useBackend } from '../backend'; +import { Box, Button, NoticeBox, Section, Tabs } from '../components'; import { Window } from '../layouts'; export const CryoStorage = (props) => { @@ -7,10 +9,10 @@ export const CryoStorage = (props) => { const { real_name, allow_items } = data; - const [tab, setTab] = useLocalState('tab', 0); + const [tab, setTab] = useState(0); return ( - + setTab(0)}> @@ -59,13 +61,15 @@ export const CryoStorageItems = (props) => { - }> + } + > {(items.length && items.map((item) => ( ))) || No items stored.} diff --git a/tgui/packages/tgui/interfaces/CryoStorageVr.jsx b/tgui/packages/tgui/interfaces/CryoStorageVr.jsx index b1edb0e9c1..173842a7a8 100644 --- a/tgui/packages/tgui/interfaces/CryoStorageVr.jsx +++ b/tgui/packages/tgui/interfaces/CryoStorageVr.jsx @@ -1,5 +1,7 @@ -import { useBackend, useLocalState } from '../backend'; -import { Box, Section, Tabs, NoticeBox } from '../components'; +import { useState } from 'react'; + +import { useBackend } from '../backend'; +import { Box, NoticeBox, Section, Tabs } from '../components'; import { Window } from '../layouts'; import { CryoStorageCrew } from './CryoStorage'; @@ -8,10 +10,10 @@ export const CryoStorageVr = (props) => { const { real_name, allow_items } = data; - const [tab, setTab] = useLocalState('tab', 0); + const [tab, setTab] = useState(0); return ( - + setTab(0)}> diff --git a/tgui/packages/tgui/interfaces/DNAForensics.jsx b/tgui/packages/tgui/interfaces/DNAForensics.jsx index 4426942bb1..b1dc785183 100644 --- a/tgui/packages/tgui/interfaces/DNAForensics.jsx +++ b/tgui/packages/tgui/interfaces/DNAForensics.jsx @@ -1,4 +1,3 @@ -import { Fragment } from 'inferno'; import { useBackend } from '../backend'; import { Box, Button, LabeledList, ProgressBar, Section } from '../components'; import { Window } from '../layouts'; @@ -12,22 +11,25 @@ export const DNAForensics = (props) => {
+ <> - - }> + + } + > { radiatingModal = ; } return ( - + {radiatingModal} @@ -50,7 +60,7 @@ const DNAModifierOccupant = (props) => {
+ <> Door Lock: @@ -67,10 +77,11 @@ const DNAModifierOccupant = (props) => { content="Eject" onClick={() => act('ejectOccupant')} /> - - }> + + } + > {hasOccupant ? ( - + <> {occupant.name} @@ -120,7 +131,7 @@ const DNAModifierOccupant = (props) => { )} - + ) : ( Cell unoccupied. )} @@ -159,17 +170,17 @@ const DNAModifierMain = (props) => { let body; if (selectedMenuKey === 'ui') { body = ( - + <> - + ); } else if (selectedMenuKey === 'se') { body = ( - + <> - + ); } else if (selectedMenuKey === 'buffer') { body = ; @@ -183,7 +194,8 @@ const DNAModifierMain = (props) => { act('selectMenuKey', { key: op[0] })}> + onClick={() => act('selectMenuKey', { key: op[0] })} + > {op[1]} @@ -305,12 +317,12 @@ const DNAModifierMainBuffers = (props) => { /> )); return ( - + <>
{bufferElements}
-
+ ); }; @@ -327,7 +339,7 @@ const DNAModifierMainBuffersElement = (props) => { mx="0" lineHeight="18px" buttons={ - + <> { }) } /> - - }> + + } + >
) : ( - + <>
{(effects && effects.map((effect) => ( @@ -95,7 +95,7 @@ const DiseaseSplicerVirusDish = (props) => { onClick={() => act('affected_species')} />
-
+ )}
); diff --git a/tgui/packages/tgui/interfaces/DishIncubator.jsx b/tgui/packages/tgui/interfaces/DishIncubator.jsx index 4f2770ae91..cd25f3e641 100644 --- a/tgui/packages/tgui/interfaces/DishIncubator.jsx +++ b/tgui/packages/tgui/interfaces/DishIncubator.jsx @@ -1,7 +1,13 @@ -import { Fragment } from 'inferno'; -import { formatCommaNumber } from '../format'; import { useBackend } from '../backend'; -import { Box, Button, Flex, LabeledList, ProgressBar, Section } from '../components'; +import { + Box, + Button, + Flex, + LabeledList, + ProgressBar, + Section, +} from '../components'; +import { formatCommaNumber } from '../format'; import { Window } from '../layouts'; export const DishIncubator = (props) => { @@ -37,7 +43,8 @@ export const DishIncubator = (props) => { content={on ? 'On' : 'Off'} onClick={() => act('power')} /> - }> + } + >
- + ); }; primaryRoutes['AirlockConsoleDocking'] = AirlockConsoleDocking; @@ -562,7 +570,7 @@ const DockingConsoleSimple = (props) => {
+ <>
- + ); }; primaryRoutes['DockingConsoleMulti'] = DockingConsoleMulti; @@ -646,7 +656,7 @@ const DoorAccessConsole = (props) => {
+ <> {/* Interior Button */}