From be4aeefce31fed78428e3adba32d88cc1ad1a5c4 Mon Sep 17 00:00:00 2001 From: CHOMPStation2 <58959929+CHOMPStation2@users.noreply.github.com> Date: Fri, 28 Jun 2024 13:05:53 -0700 Subject: [PATCH] [MIRROR] Converts the first few of our UIs to typescript (#8588) Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com> --- code/controllers/configuration.dm | 13 +- code/datums/datacore.dm | 1 - code/game/machinery/adv_med.dm | 5 + code/game/machinery/cryopod.dm | 2 +- code/modules/mob/living/bot/cleanbot.dm | 8 +- code/modules/mob/living/bot/edCLNbot.dm | 2 +- .../silicon/robot/drone/drone_console.dm | 7 +- code/modules/pda/cart_apps.dm | 2 +- code/modules/pda/core_apps.dm | 5 +- code/modules/power/apc.dm | 2 +- .../machinery/dispenser/dispenser2.dm | 2 +- code/modules/recycling/disposal.dm | 4 +- code/modules/recycling/sortingmachinery.dm | 14 +- code/modules/shuttles/escape_pods.dm | 6 +- .../tgui/modules/appearance_changer.dm | 28 +- code/modules/virus2/diseasesplicer.dm | 3 +- .../xenobio2/machinery/injector_computer.dm | 1 + tgui/packages/common/type-utils.ts | 41 + tgui/packages/tgui/backend.ts | 2 + tgui/packages/tgui/components/Collapsible.tsx | 5 +- tgui/packages/tgui/components/NoticeBox.tsx | 8 +- tgui/packages/tgui/components/Tabs.tsx | 6 +- .../interfaces/{AICard.jsx => AICard.tsx} | 27 +- .../tgui/interfaces/{APC.jsx => APC.tsx} | 156 +- ...ountsTerminal.jsx => AccountsTerminal.tsx} | 61 +- .../interfaces/AdminShuttleController.tsx | 4 +- .../{AiAirlock.jsx => AiAirlock.tsx} | 156 +- .../{AiRestorer.jsx => AiRestorer.tsx} | 17 +- .../{AiSupermatter.jsx => AiSupermatter.tsx} | 18 +- .../interfaces/{AirAlarm.jsx => AirAlarm.tsx} | 110 +- .../{AlgaeFarm.jsx => AlgaeFarm.tsx} | 24 +- .../tgui/interfaces/AppearanceChanger.jsx | 523 ------- .../AppearanceChangerBody.tsx | 139 ++ .../AppearanceChangerDetails.tsx | 142 ++ .../AppearanceChangerHairs.tsx | 45 + .../interfaces/AppearanceChanger/index.tsx | 269 ++++ .../interfaces/AppearanceChanger/types.ts | 53 + .../{ArcadeBattle.jsx => ArcadeBattle.tsx} | 17 +- ...berControl.jsx => AreaScrubberControl.tsx} | 23 +- .../{AssemblyProx.jsx => AssemblyProx.tsx} | 17 +- .../{AssemblyTimer.jsx => AssemblyTimer.tsx} | 8 +- ...AlertConsole.jsx => AtmosAlertConsole.tsx} | 19 +- .../{AtmosControl.jsx => AtmosControl.tsx} | 20 +- .../{AtmosFilter.jsx => AtmosFilter.tsx} | 32 +- .../{AtmosMixer.jsx => AtmosMixer.tsx} | 51 +- .../{Autolathe.jsx => Autolathe.tsx} | 33 +- .../{Batteryrack.jsx => Batteryrack.tsx} | 23 +- .../{BeaconLocator.jsx => BeaconLocator.tsx} | 14 +- .../{Biogenerator.jsx => Biogenerator.tsx} | 97 +- .../BodyDesigner/BodyDesignerBodyRecords.tsx | 32 + .../BodyDesigner/BodyDesignerMain.tsx | 16 + .../BodyDesigner/BodyDesignerOOCNotes.tsx | 29 + .../BodyDesignerSpecificRecord.tsx} | 150 +- .../BodyDesigner/BodyDesignerStockRecords.tsx | 29 + .../tgui/interfaces/BodyDesigner/index.tsx | 71 + .../tgui/interfaces/BodyDesigner/types.ts | 63 + tgui/packages/tgui/interfaces/BodyScanner.jsx | 512 ------- .../BodyScanner/BodyScannerEmpty.tsx | 15 + .../BodyScanner/BodyScannerMain.tsx | 22 + .../BodyScannerMainAbnormalities.tsx | 42 + .../BodyScanner/BodyScannerMainDamage.tsx | 54 + .../BodyScanner/BodyScannerMainOccupant.tsx | 81 + .../BodyScannerMainOrgansExternal.tsx | 99 ++ .../BodyScannerMainOrgansInternal.tsx | 63 + .../BodyScanner/BodyScannerMainReagents.tsx | 53 + .../tgui/interfaces/BodyScanner/constants.ts | 62 + .../tgui/interfaces/BodyScanner/functions.tsx | 58 + .../tgui/interfaces/BodyScanner/index.tsx | 23 + .../tgui/interfaces/BodyScanner/types.ts | 77 + .../{BombTester.jsx => BombTester.tsx} | 38 +- .../{BotanyEditor.jsx => BotanyEditor.tsx} | 13 +- ...{BotanyIsolator.jsx => BotanyIsolator.tsx} | 14 +- .../{BrigTimer.jsx => BrigTimer.tsx} | 52 +- .../{CameraConsole.jsx => CameraConsole.tsx} | 65 +- .../interfaces/{Canister.jsx => Canister.tsx} | 17 +- .../interfaces/{Canvas.jsx => Canvas.tsx} | 49 +- ...Dispenser.jsx => CasinoPrizeDispenser.tsx} | 48 +- ...erDirectory.jsx => CharacterDirectory.tsx} | 71 +- .../tgui/interfaces/CheckboxInput.tsx | 2 +- .../tgui/interfaces/ChemDispenser.jsx | 175 --- .../ChemDispenser/ChemDispenserBeaker.tsx | 81 + .../ChemDispenser/ChemDispenserChemicals.tsx | 41 + .../ChemDispenser/ChemDispenserSettings.tsx | 46 + .../interfaces/ChemDispenser/constants.ts | 2 + .../tgui/interfaces/ChemDispenser/index.tsx | 16 + .../tgui/interfaces/ChemDispenser/types.ts | 13 + .../ChemMasterAnalyzeModalBodyOverride.tsx | 53 + .../ChemMaster/ChemMasterBeaker.tsx | 94 ++ .../ChemMaster/ChemMasterBuffer.tsx | 92 ++ .../ChemMaster/ChemMasterCustomization.tsx | 55 + .../ChemMaster/ChemMasterProduction.tsx | 97 ++ .../ChemMasterProductionChemical.tsx | 111 ++ .../ChemMasterProductionCondiment.tsx | 26 + .../tgui/interfaces/ChemMaster/constants.ts | 8 + .../tgui/interfaces/ChemMaster/index.tsx | 65 + .../tgui/interfaces/ChemMaster/types.ts | 40 + .../{ClawMachine.jsx => ClawMachine.tsx} | 11 +- .../interfaces/{Cleanbot.jsx => Cleanbot.tsx} | 21 +- .../tgui/interfaces/CloningConsole.jsx | 458 ------ .../CloningConsoleBodyOverride.tsx | 115 ++ .../CloningConsoleNavigation.tsx | 34 + .../CloningConsole/CloningConsoleStatus.tsx | 92 ++ .../CloningConsole/CloningConsoleTabs.tsx | 192 +++ .../tgui/interfaces/CloningConsole/index.tsx | 44 + .../tgui/interfaces/CloningConsole/types.ts | 41 + tgui/packages/tgui/interfaces/ColorMate.jsx | 418 ------ .../interfaces/ColorMate/ColorMateColor.tsx | 76 + .../interfaces/ColorMate/ColorMateMatrix.tsx | 235 +++ .../tgui/interfaces/ColorMate/index.tsx | 120 ++ .../tgui/interfaces/ColorMate/types.ts | 22 + .../tgui/interfaces/CommunicationsConsole.jsx | 364 ----- .../CommunicationsConsoleAuth.tsx | 74 + .../CommunicationsConsoleContent.tsx | 37 + .../CommunicationsConsoleMain.tsx | 132 ++ .../CommunicationsConsoleMessage.tsx | 69 + .../CommunicationsConsoleStatusDisplay.tsx | 54 + .../CommunicationsConsole/index.tsx | 12 + .../interfaces/CommunicationsConsole/types.ts | 33 + .../packages/tgui/interfaces/Communicator.tsx | 1307 ----------------- .../Communicator/CommunicatorContactTab.tsx | 67 + .../Communicator/CommunicatorGeneral.tsx | 209 +++ .../Communicator/CommunicatorHomeTab.tsx | 67 + .../CommunicatorMessageSubTab.tsx | 190 +++ .../Communicator/CommunicatorMessageTab.tsx | 63 + .../Communicator/CommunicatorNewsTab.tsx | 105 ++ .../Communicator/CommunicatorNoteTab.tsx | 34 + .../Communicator/CommunicatorPhoneTab.tsx | 328 +++++ .../Communicator/CommunicatorSettingsTab.tsx | 74 + .../Communicator/CommunicatorWeatherTab.tsx | 97 ++ .../tgui/interfaces/Communicator/constants.ts | 27 + .../tgui/interfaces/Communicator/index.tsx | 88 ++ .../tgui/interfaces/Communicator/types.ts | 96 ++ .../interfaces/ComputerFabricator/CfStep1.tsx | 53 + .../CfStep2.tsx} | 162 +- .../interfaces/ComputerFabricator/CfStep3.tsx | 19 + .../interfaces/ComputerFabricator/CfStep4.tsx | 15 + .../interfaces/ComputerFabricator/index.tsx | 37 + .../interfaces/ComputerFabricator/types.ts | 14 + ...kingAppliance.jsx => CookingAppliance.tsx} | 18 +- tgui/packages/tgui/interfaces/CrewMonitor.jsx | 188 --- tgui/packages/tgui/interfaces/CrewMonitor.tsx | 231 +++ .../{Cryo.jsx => Cryo/CryoContent.tsx} | 61 +- .../tgui/interfaces/Cryo/constants.ts | 24 + tgui/packages/tgui/interfaces/Cryo/index.tsx | 12 + tgui/packages/tgui/interfaces/Cryo/types.ts | 23 + .../{CryoStorage.jsx => CryoStorage.tsx} | 47 +- .../tgui/interfaces/CryoStorageVr.jsx | 51 - .../{DNAForensics.jsx => DNAForensics.tsx} | 11 +- tgui/packages/tgui/interfaces/DNAModifier.jsx | 701 --------- .../DNAModifier/DNAModifierBlocks.tsx | 59 + .../DNAModifier/DNAModifierIrradiating.tsx | 22 + .../DNAModifier/DNAModifierMain.tsx | 265 ++++ .../DNAModifier/DNAModifierMainBuffers.tsx | 254 ++++ .../DNAModifier/DNAModifierOccupant.tsx | 103 ++ .../tgui/interfaces/DNAModifier/constants.ts | 14 + .../tgui/interfaces/DNAModifier/index.tsx | 29 + .../tgui/interfaces/DNAModifier/types.ts | 54 + .../tgui/interfaces/DestinationTagger.jsx | 31 - .../tgui/interfaces/DestinationTagger.tsx | 50 + ...{DiseaseSplicer.jsx => DiseaseSplicer.tsx} | 66 +- .../{DishIncubator.jsx => DishIncubator.tsx} | 22 +- .../{DroneConsole.jsx => DroneConsole.tsx} | 21 +- .../tgui/interfaces/EmbeddedController.jsx | 757 ---------- .../AirlockConsoleAdvanced.tsx | 85 ++ .../AirlockConsoleDocking.tsx | 84 ++ .../AirlockConsolePhoron.tsx | 70 + .../AirlockConsoleSimple.tsx | 55 + .../DockingConsoleMulti.tsx | 47 + .../DockingConsoleSimple.tsx | 50 + .../EmbeddedController/DoorAccessConsole.tsx | 55 + .../EmbeddedControllerHelpers.tsx | 284 ++++ .../EscapePodBerthConsole.tsx | 33 + .../EmbeddedController/EscapePodConsole.tsx | 57 + .../interfaces/EmbeddedController/index.tsx | 110 ++ .../interfaces/EmbeddedController/types.ts | 82 ++ .../interfaces/{Farmbot.jsx => Farmbot.tsx} | 26 +- .../tgui/interfaces/{Fax.jsx => Fax.tsx} | 30 +- .../{FileCabinet.jsx => FileCabinet.tsx} | 8 +- .../interfaces/{Floorbot.jsx => Floorbot.tsx} | 20 +- .../interfaces/{GasPump.jsx => GasPump.tsx} | 22 +- ...ureSystem.jsx => GasTemperatureSystem.tsx} | 16 +- .../tgui/interfaces/{Gps.jsx => Gps.tsx} | 48 +- ...vityGenerator.jsx => GravityGenerator.tsx} | 16 +- .../{GuestPass.jsx => GuestPass.tsx} | 28 +- .../interfaces/{Holodeck.jsx => Holodeck.tsx} | 14 +- .../{ICAssembly.jsx => ICAssembly.tsx} | 18 +- .../{ICCircuit.jsx => ICCircuit.tsx} | 47 +- .../{ICDetailer.jsx => ICDetailer.tsx} | 8 +- .../{ICPrinter.jsx => ICPrinter.tsx} | 66 +- .../interfaces/{IDCard.jsx => IDCard.tsx} | 30 +- ...omputer.jsx => IdentificationComputer.tsx} | 122 +- ...PanelHuman.jsx => InventoryPanelHuman.tsx} | 25 +- ...Centrifuge.jsx => IsolationCentrifuge.tsx} | 32 +- .../{JanitorCart.jsx => JanitorCart.tsx} | 51 +- .../interfaces/{Jukebox.jsx => Jukebox.tsx} | 61 +- .../{LawManager.jsx => LawManager.tsx} | 277 ++-- .../{LookingGlass.jsx => LookingGlass.tsx} | 11 +- ...rolConsole.jsx => MechaControlConsole.tsx} | 40 +- .../interfaces/{Medbot.jsx => Medbot.tsx} | 27 +- .../tgui/interfaces/MentorTicketPanel.tsx | 1 - .../tgui/interfaces/MessageMonitor.jsx | 440 ------ .../MessageMonitor/MessageMonitorContent.tsx | 73 + .../MessageMonitor/MessageMonitorHack.tsx | 93 ++ .../MessageMonitor/MessageMonitorLogin.tsx | 40 + .../MessageMonitor/MessageMonitorTabs.tsx | 230 +++ .../tgui/interfaces/MessageMonitor/index.tsx | 34 + .../tgui/interfaces/MessageMonitor/types.ts | 38 + .../{Microwave.jsx => Microwave.tsx} | 12 +- ...ole.jsx => MiningOreProcessingConsole.tsx} | 27 +- ...gConsole.jsx => MiningStackingConsole.tsx} | 7 +- .../{MiningVendor.jsx => MiningVendor.tsx} | 63 +- tgui/packages/tgui/interfaces/MobSpawner.tsx | 102 +- .../{NTNetRelay.jsx => NTNetRelay.tsx} | 21 +- .../tgui/interfaces/NtosAccessDecrypter.tsx | 2 +- .../{NtosArcade.jsx => NtosArcade.tsx} | 61 +- ...ameraConsole.jsx => NtosCameraConsole.tsx} | 52 +- .../interfaces/NtosCommunicationsConsole.tsx | 2 +- tgui/packages/tgui/interfaces/Pda.jsx | 4 +- .../{PipeDispenser.jsx => PipeDispenser.tsx} | 23 +- .../{PlantAnalyzer.jsx => PlantAnalyzer.tsx} | 35 +- .../tgui/interfaces/PointDefenseControl.tsx | 6 +- ...bleGenerator.jsx => PortableGenerator.tsx} | 86 +- .../{PortablePump.jsx => PortablePump.tsx} | 12 +- ...{PortableTurret.jsx => PortableTurret.tsx} | 20 +- .../tgui/interfaces/ResearchConsole.jsx | 921 ------------ .../ResearchConsoleBuildMenu.tsx | 99 ++ .../ResearchConsoleConstructor.tsx | 139 ++ .../ResearchConsoleConstructorTabs.tsx | 201 +++ .../ResearchConsoleDestructiveAnalyzer.tsx | 56 + .../ResearchConsole/ResearchConsoleDisk.tsx | 217 +++ .../ResearchConsoleSettings.tsx | 139 ++ .../ResearchConsoleViewDesigns.tsx | 45 + .../ResearchConsoleViewResearch.tsx | 34 + .../interfaces/ResearchConsole/constants.ts | 37 + .../tgui/interfaces/ResearchConsole/index.tsx | 128 ++ .../tgui/interfaces/ResearchConsole/types.ts | 76 + .../{TEGenerator.jsx => TEGenerator.tsx} | 22 +- .../tgui/interfaces/{Tank.jsx => Tank.tsx} | 17 +- .../tgui/interfaces/TraitTutorial.tsx | 6 +- ...{TurbineControl.jsx => TurbineControl.tsx} | 17 +- .../tgui/interfaces/{Wires.jsx => Wires.tsx} | 20 +- ...ster.jsx => XenoarchArtifactHarvester.tsx} | 37 +- .../XenoarchHandheldPowerUtilizer.tsx | 18 +- .../tgui/interfaces/XenoarchReplicator.tsx | 3 +- ...jsx => XenoarchReplicator_clothing_vr.tsx} | 7 +- ....jsx => XenoarchReplicator_voremob_vr.tsx} | 7 +- .../{AtmosControls.jsx => AtmosControls.tsx} | 13 +- ...{BeakerContents.jsx => BeakerContents.tsx} | 0 .../tgui/interfaces/common/CommonTypes.ts | 39 + .../{ComplexModal.jsx => ComplexModal.tsx} | 123 +- ...lscreenNotice.jsx => FullscreenNotice.tsx} | 5 +- ...ticeBox.jsx => InterfaceLockNoticeBox.tsx} | 10 +- .../common/{LoginInfo.jsx => LoginInfo.tsx} | 16 +- .../{LoginScreen.jsx => LoginScreen.tsx} | 19 +- .../common/{Mining.jsx => Mining.tsx} | 6 +- .../common/{Overmap.jsx => Overmap.tsx} | 14 +- .../{PortableAtmos.jsx => PortableAtmos.tsx} | 14 +- .../common/{RankIcon.jsx => RankIcon.tsx} | 4 +- ...emporaryNotice.jsx => TemporaryNotice.tsx} | 13 +- ...{pda_atmos_scan.jsx => pda_atmos_scan.tsx} | 18 +- .../pda/{pda_janitor.jsx => pda_janitor.tsx} | 36 +- .../{pda_main_menu.jsx => pda_main_menu.tsx} | 35 +- .../{pda_manifest.jsx => pda_manifest.tsx} | 0 .../pda/{pda_medical.jsx => pda_medical.tsx} | 36 +- .../{pda_messenger.jsx => pda_messenger.tsx} | 115 +- .../pda/{pda_news.jsx => pda_news.tsx} | 56 +- ...{pda_notekeeper.jsx => pda_notekeeper.tsx} | 4 +- .../pda/{pda_power.jsx => pda_power.tsx} | 0 .../{pda_security.jsx => pda_security.tsx} | 28 +- .../{pda_signaller.jsx => pda_signaller.tsx} | 0 ...tus_display.jsx => pda_status_display.tsx} | 8 +- .../pda/{pda_supply.jsx => pda_supply.tsx} | 24 +- .../packages/tgui/interfaces/pda/pda_types.ts | 30 + .../tgui/layouts/{Pane.jsx => Pane.tsx} | 33 +- 274 files changed, 12200 insertions(+), 8304 deletions(-) create mode 100644 tgui/packages/common/type-utils.ts rename tgui/packages/tgui/interfaces/{AICard.jsx => AICard.tsx} (86%) rename tgui/packages/tgui/interfaces/{APC.jsx => APC.tsx} (67%) rename tgui/packages/tgui/interfaces/{AccountsTerminal.jsx => AccountsTerminal.tsx} (82%) rename tgui/packages/tgui/interfaces/{AiAirlock.jsx => AiAirlock.tsx} (54%) rename tgui/packages/tgui/interfaces/{AiRestorer.jsx => AiRestorer.tsx} (87%) rename tgui/packages/tgui/interfaces/{AiSupermatter.jsx => AiSupermatter.tsx} (87%) rename tgui/packages/tgui/interfaces/{AirAlarm.jsx => AirAlarm.tsx} (78%) rename tgui/packages/tgui/interfaces/{AlgaeFarm.jsx => AlgaeFarm.tsx} (88%) delete mode 100644 tgui/packages/tgui/interfaces/AppearanceChanger.jsx create mode 100644 tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx create mode 100644 tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerDetails.tsx create mode 100644 tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerHairs.tsx create mode 100644 tgui/packages/tgui/interfaces/AppearanceChanger/index.tsx create mode 100644 tgui/packages/tgui/interfaces/AppearanceChanger/types.ts rename tgui/packages/tgui/interfaces/{ArcadeBattle.jsx => ArcadeBattle.tsx} (93%) rename tgui/packages/tgui/interfaces/{AreaScrubberControl.jsx => AreaScrubberControl.tsx} (88%) rename tgui/packages/tgui/interfaces/{AssemblyProx.jsx => AssemblyProx.tsx} (81%) rename tgui/packages/tgui/interfaces/{AssemblyTimer.jsx => AssemblyTimer.tsx} (81%) rename tgui/packages/tgui/interfaces/{AtmosAlertConsole.jsx => AtmosAlertConsole.tsx} (74%) rename tgui/packages/tgui/interfaces/{AtmosControl.jsx => AtmosControl.tsx} (88%) rename tgui/packages/tgui/interfaces/{AtmosFilter.jsx => AtmosFilter.tsx} (73%) rename tgui/packages/tgui/interfaces/{AtmosMixer.jsx => AtmosMixer.tsx} (67%) rename tgui/packages/tgui/interfaces/{Autolathe.jsx => Autolathe.tsx} (81%) rename tgui/packages/tgui/interfaces/{Batteryrack.jsx => Batteryrack.tsx} (89%) rename tgui/packages/tgui/interfaces/{BeaconLocator.jsx => BeaconLocator.tsx} (86%) rename tgui/packages/tgui/interfaces/{Biogenerator.jsx => Biogenerator.tsx} (66%) create mode 100644 tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerBodyRecords.tsx create mode 100644 tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerMain.tsx create mode 100644 tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerOOCNotes.tsx rename tgui/packages/tgui/interfaces/{BodyDesigner.jsx => BodyDesigner/BodyDesignerSpecificRecord.tsx} (66%) create mode 100644 tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerStockRecords.tsx create mode 100644 tgui/packages/tgui/interfaces/BodyDesigner/index.tsx create mode 100644 tgui/packages/tgui/interfaces/BodyDesigner/types.ts delete mode 100644 tgui/packages/tgui/interfaces/BodyScanner.jsx create mode 100644 tgui/packages/tgui/interfaces/BodyScanner/BodyScannerEmpty.tsx create mode 100644 tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMain.tsx create mode 100644 tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainAbnormalities.tsx create mode 100644 tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainDamage.tsx create mode 100644 tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOccupant.tsx create mode 100644 tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOrgansExternal.tsx create mode 100644 tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOrgansInternal.tsx create mode 100644 tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainReagents.tsx create mode 100644 tgui/packages/tgui/interfaces/BodyScanner/constants.ts create mode 100644 tgui/packages/tgui/interfaces/BodyScanner/functions.tsx create mode 100644 tgui/packages/tgui/interfaces/BodyScanner/index.tsx create mode 100644 tgui/packages/tgui/interfaces/BodyScanner/types.ts rename tgui/packages/tgui/interfaces/{BombTester.jsx => BombTester.tsx} (88%) rename tgui/packages/tgui/interfaces/{BotanyEditor.jsx => BotanyEditor.tsx} (88%) rename tgui/packages/tgui/interfaces/{BotanyIsolator.jsx => BotanyIsolator.tsx} (91%) rename tgui/packages/tgui/interfaces/{BrigTimer.jsx => BrigTimer.tsx} (60%) rename tgui/packages/tgui/interfaces/{CameraConsole.jsx => CameraConsole.tsx} (74%) rename tgui/packages/tgui/interfaces/{Canister.jsx => Canister.tsx} (91%) rename tgui/packages/tgui/interfaces/{Canvas.jsx => Canvas.tsx} (69%) rename tgui/packages/tgui/interfaces/{CasinoPrizeDispenser.jsx => CasinoPrizeDispenser.tsx} (81%) rename tgui/packages/tgui/interfaces/{CharacterDirectory.jsx => CharacterDirectory.tsx} (81%) delete mode 100644 tgui/packages/tgui/interfaces/ChemDispenser.jsx create mode 100644 tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserBeaker.tsx create mode 100644 tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserChemicals.tsx create mode 100644 tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserSettings.tsx create mode 100644 tgui/packages/tgui/interfaces/ChemDispenser/constants.ts create mode 100644 tgui/packages/tgui/interfaces/ChemDispenser/index.tsx create mode 100644 tgui/packages/tgui/interfaces/ChemDispenser/types.ts create mode 100644 tgui/packages/tgui/interfaces/ChemMaster/ChemMasterAnalyzeModalBodyOverride.tsx create mode 100644 tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBeaker.tsx create mode 100644 tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBuffer.tsx create mode 100644 tgui/packages/tgui/interfaces/ChemMaster/ChemMasterCustomization.tsx create mode 100644 tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProduction.tsx create mode 100644 tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProductionChemical.tsx create mode 100644 tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProductionCondiment.tsx create mode 100644 tgui/packages/tgui/interfaces/ChemMaster/constants.ts create mode 100644 tgui/packages/tgui/interfaces/ChemMaster/index.tsx create mode 100644 tgui/packages/tgui/interfaces/ChemMaster/types.ts rename tgui/packages/tgui/interfaces/{ClawMachine.jsx => ClawMachine.tsx} (91%) rename tgui/packages/tgui/interfaces/{Cleanbot.jsx => Cleanbot.tsx} (91%) delete mode 100644 tgui/packages/tgui/interfaces/CloningConsole.jsx create mode 100644 tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleBodyOverride.tsx create mode 100644 tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleNavigation.tsx create mode 100644 tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleStatus.tsx create mode 100644 tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleTabs.tsx create mode 100644 tgui/packages/tgui/interfaces/CloningConsole/index.tsx create mode 100644 tgui/packages/tgui/interfaces/CloningConsole/types.ts delete mode 100644 tgui/packages/tgui/interfaces/ColorMate.jsx create mode 100644 tgui/packages/tgui/interfaces/ColorMate/ColorMateColor.tsx create mode 100644 tgui/packages/tgui/interfaces/ColorMate/ColorMateMatrix.tsx create mode 100644 tgui/packages/tgui/interfaces/ColorMate/index.tsx create mode 100644 tgui/packages/tgui/interfaces/ColorMate/types.ts delete mode 100644 tgui/packages/tgui/interfaces/CommunicationsConsole.jsx create mode 100644 tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleAuth.tsx create mode 100644 tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleContent.tsx create mode 100644 tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleMain.tsx create mode 100644 tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleMessage.tsx create mode 100644 tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleStatusDisplay.tsx create mode 100644 tgui/packages/tgui/interfaces/CommunicationsConsole/index.tsx create mode 100644 tgui/packages/tgui/interfaces/CommunicationsConsole/types.ts delete mode 100644 tgui/packages/tgui/interfaces/Communicator.tsx create mode 100644 tgui/packages/tgui/interfaces/Communicator/CommunicatorContactTab.tsx create mode 100644 tgui/packages/tgui/interfaces/Communicator/CommunicatorGeneral.tsx create mode 100644 tgui/packages/tgui/interfaces/Communicator/CommunicatorHomeTab.tsx create mode 100644 tgui/packages/tgui/interfaces/Communicator/CommunicatorMessageSubTab.tsx create mode 100644 tgui/packages/tgui/interfaces/Communicator/CommunicatorMessageTab.tsx create mode 100644 tgui/packages/tgui/interfaces/Communicator/CommunicatorNewsTab.tsx create mode 100644 tgui/packages/tgui/interfaces/Communicator/CommunicatorNoteTab.tsx create mode 100644 tgui/packages/tgui/interfaces/Communicator/CommunicatorPhoneTab.tsx create mode 100644 tgui/packages/tgui/interfaces/Communicator/CommunicatorSettingsTab.tsx create mode 100644 tgui/packages/tgui/interfaces/Communicator/CommunicatorWeatherTab.tsx create mode 100644 tgui/packages/tgui/interfaces/Communicator/constants.ts create mode 100644 tgui/packages/tgui/interfaces/Communicator/index.tsx create mode 100644 tgui/packages/tgui/interfaces/Communicator/types.ts create mode 100644 tgui/packages/tgui/interfaces/ComputerFabricator/CfStep1.tsx rename tgui/packages/tgui/interfaces/{ComputerFabricator.jsx => ComputerFabricator/CfStep2.tsx} (67%) create mode 100644 tgui/packages/tgui/interfaces/ComputerFabricator/CfStep3.tsx create mode 100644 tgui/packages/tgui/interfaces/ComputerFabricator/CfStep4.tsx create mode 100644 tgui/packages/tgui/interfaces/ComputerFabricator/index.tsx create mode 100644 tgui/packages/tgui/interfaces/ComputerFabricator/types.ts rename tgui/packages/tgui/interfaces/{CookingAppliance.jsx => CookingAppliance.tsx} (87%) delete mode 100644 tgui/packages/tgui/interfaces/CrewMonitor.jsx create mode 100644 tgui/packages/tgui/interfaces/CrewMonitor.tsx rename tgui/packages/tgui/interfaces/{Cryo.jsx => Cryo/CryoContent.tsx} (78%) create mode 100644 tgui/packages/tgui/interfaces/Cryo/constants.ts create mode 100644 tgui/packages/tgui/interfaces/Cryo/index.tsx create mode 100644 tgui/packages/tgui/interfaces/Cryo/types.ts rename tgui/packages/tgui/interfaces/{CryoStorage.jsx => CryoStorage.tsx} (61%) delete mode 100644 tgui/packages/tgui/interfaces/CryoStorageVr.jsx rename tgui/packages/tgui/interfaces/{DNAForensics.jsx => DNAForensics.tsx} (89%) delete mode 100644 tgui/packages/tgui/interfaces/DNAModifier.jsx create mode 100644 tgui/packages/tgui/interfaces/DNAModifier/DNAModifierBlocks.tsx create mode 100644 tgui/packages/tgui/interfaces/DNAModifier/DNAModifierIrradiating.tsx create mode 100644 tgui/packages/tgui/interfaces/DNAModifier/DNAModifierMain.tsx create mode 100644 tgui/packages/tgui/interfaces/DNAModifier/DNAModifierMainBuffers.tsx create mode 100644 tgui/packages/tgui/interfaces/DNAModifier/DNAModifierOccupant.tsx create mode 100644 tgui/packages/tgui/interfaces/DNAModifier/constants.ts create mode 100644 tgui/packages/tgui/interfaces/DNAModifier/index.tsx create mode 100644 tgui/packages/tgui/interfaces/DNAModifier/types.ts delete mode 100644 tgui/packages/tgui/interfaces/DestinationTagger.jsx create mode 100644 tgui/packages/tgui/interfaces/DestinationTagger.tsx rename tgui/packages/tgui/interfaces/{DiseaseSplicer.jsx => DiseaseSplicer.tsx} (76%) rename tgui/packages/tgui/interfaces/{DishIncubator.jsx => DishIncubator.tsx} (91%) rename tgui/packages/tgui/interfaces/{DroneConsole.jsx => DroneConsole.tsx} (88%) delete mode 100644 tgui/packages/tgui/interfaces/EmbeddedController.jsx create mode 100644 tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsoleAdvanced.tsx create mode 100644 tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsoleDocking.tsx create mode 100644 tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsolePhoron.tsx create mode 100644 tgui/packages/tgui/interfaces/EmbeddedController/AirlockConsoleSimple.tsx create mode 100644 tgui/packages/tgui/interfaces/EmbeddedController/DockingConsoleMulti.tsx create mode 100644 tgui/packages/tgui/interfaces/EmbeddedController/DockingConsoleSimple.tsx create mode 100644 tgui/packages/tgui/interfaces/EmbeddedController/DoorAccessConsole.tsx create mode 100644 tgui/packages/tgui/interfaces/EmbeddedController/EmbeddedControllerHelpers.tsx create mode 100644 tgui/packages/tgui/interfaces/EmbeddedController/EscapePodBerthConsole.tsx create mode 100644 tgui/packages/tgui/interfaces/EmbeddedController/EscapePodConsole.tsx create mode 100644 tgui/packages/tgui/interfaces/EmbeddedController/index.tsx create mode 100644 tgui/packages/tgui/interfaces/EmbeddedController/types.ts rename tgui/packages/tgui/interfaces/{Farmbot.jsx => Farmbot.tsx} (87%) rename tgui/packages/tgui/interfaces/{Fax.jsx => Fax.tsx} (84%) rename tgui/packages/tgui/interfaces/{FileCabinet.jsx => FileCabinet.tsx} (78%) rename tgui/packages/tgui/interfaces/{Floorbot.jsx => Floorbot.tsx} (89%) rename tgui/packages/tgui/interfaces/{GasPump.jsx => GasPump.tsx} (79%) rename tgui/packages/tgui/interfaces/{GasTemperatureSystem.jsx => GasTemperatureSystem.tsx} (87%) rename tgui/packages/tgui/interfaces/{Gps.jsx => Gps.tsx} (81%) rename tgui/packages/tgui/interfaces/{GravityGenerator.jsx => GravityGenerator.tsx} (85%) rename tgui/packages/tgui/interfaces/{GuestPass.jsx => GuestPass.tsx} (82%) rename tgui/packages/tgui/interfaces/{Holodeck.jsx => Holodeck.tsx} (87%) rename tgui/packages/tgui/interfaces/{ICAssembly.jsx => ICAssembly.tsx} (91%) rename tgui/packages/tgui/interfaces/{ICCircuit.jsx => ICCircuit.tsx} (82%) rename tgui/packages/tgui/interfaces/{ICDetailer.jsx => ICDetailer.tsx} (84%) rename tgui/packages/tgui/interfaces/{ICPrinter.jsx => ICPrinter.tsx} (71%) rename tgui/packages/tgui/interfaces/{IDCard.jsx => IDCard.tsx} (77%) rename tgui/packages/tgui/interfaces/{IdentificationComputer.jsx => IdentificationComputer.tsx} (69%) rename tgui/packages/tgui/interfaces/{InventoryPanelHuman.jsx => InventoryPanelHuman.tsx} (87%) rename tgui/packages/tgui/interfaces/{IsolationCentrifuge.jsx => IsolationCentrifuge.tsx} (84%) rename tgui/packages/tgui/interfaces/{JanitorCart.jsx => JanitorCart.tsx} (75%) rename tgui/packages/tgui/interfaces/{Jukebox.jsx => Jukebox.tsx} (86%) rename tgui/packages/tgui/interfaces/{LawManager.jsx => LawManager.tsx} (61%) rename tgui/packages/tgui/interfaces/{LookingGlass.jsx => LookingGlass.tsx} (88%) rename tgui/packages/tgui/interfaces/{MechaControlConsole.jsx => MechaControlConsole.tsx} (86%) rename tgui/packages/tgui/interfaces/{Medbot.jsx => Medbot.tsx} (85%) delete mode 100644 tgui/packages/tgui/interfaces/MessageMonitor.jsx create mode 100644 tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorContent.tsx create mode 100644 tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorHack.tsx create mode 100644 tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorLogin.tsx create mode 100644 tgui/packages/tgui/interfaces/MessageMonitor/MessageMonitorTabs.tsx create mode 100644 tgui/packages/tgui/interfaces/MessageMonitor/index.tsx create mode 100644 tgui/packages/tgui/interfaces/MessageMonitor/types.ts rename tgui/packages/tgui/interfaces/{Microwave.jsx => Microwave.tsx} (88%) rename tgui/packages/tgui/interfaces/{MiningOreProcessingConsole.jsx => MiningOreProcessingConsole.tsx} (88%) rename tgui/packages/tgui/interfaces/{MiningStackingConsole.jsx => MiningStackingConsole.tsx} (92%) rename tgui/packages/tgui/interfaces/{MiningVendor.jsx => MiningVendor.tsx} (74%) rename tgui/packages/tgui/interfaces/{NTNetRelay.jsx => NTNetRelay.tsx} (84%) rename tgui/packages/tgui/interfaces/{NtosArcade.jsx => NtosArcade.tsx} (75%) rename tgui/packages/tgui/interfaces/{NtosCameraConsole.jsx => NtosCameraConsole.tsx} (59%) rename tgui/packages/tgui/interfaces/{PipeDispenser.jsx => PipeDispenser.tsx} (83%) rename tgui/packages/tgui/interfaces/{PlantAnalyzer.jsx => PlantAnalyzer.tsx} (76%) rename tgui/packages/tgui/interfaces/{PortableGenerator.jsx => PortableGenerator.tsx} (55%) rename tgui/packages/tgui/interfaces/{PortablePump.jsx => PortablePump.tsx} (90%) rename tgui/packages/tgui/interfaces/{PortableTurret.jsx => PortableTurret.tsx} (88%) delete mode 100644 tgui/packages/tgui/interfaces/ResearchConsole.jsx create mode 100644 tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleBuildMenu.tsx create mode 100644 tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleConstructor.tsx create mode 100644 tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleConstructorTabs.tsx create mode 100644 tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleDestructiveAnalyzer.tsx create mode 100644 tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleDisk.tsx create mode 100644 tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleSettings.tsx create mode 100644 tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleViewDesigns.tsx create mode 100644 tgui/packages/tgui/interfaces/ResearchConsole/ResearchConsoleViewResearch.tsx create mode 100644 tgui/packages/tgui/interfaces/ResearchConsole/constants.ts create mode 100644 tgui/packages/tgui/interfaces/ResearchConsole/index.tsx create mode 100644 tgui/packages/tgui/interfaces/ResearchConsole/types.ts rename tgui/packages/tgui/interfaces/{TEGenerator.jsx => TEGenerator.tsx} (85%) rename tgui/packages/tgui/interfaces/{Tank.jsx => Tank.tsx} (88%) rename tgui/packages/tgui/interfaces/{TurbineControl.jsx => TurbineControl.tsx} (90%) rename tgui/packages/tgui/interfaces/{Wires.jsx => Wires.tsx} (86%) rename tgui/packages/tgui/interfaces/{XenoarchArtifactHarvester.jsx => XenoarchArtifactHarvester.tsx} (78%) rename tgui/packages/tgui/interfaces/{XenoarchReplicator_clothing_vr.jsx => XenoarchReplicator_clothing_vr.tsx} (81%) rename tgui/packages/tgui/interfaces/{XenoarchReplicator_voremob_vr.jsx => XenoarchReplicator_voremob_vr.tsx} (81%) rename tgui/packages/tgui/interfaces/common/{AtmosControls.jsx => AtmosControls.tsx} (94%) rename tgui/packages/tgui/interfaces/common/{BeakerContents.jsx => BeakerContents.tsx} (100%) create mode 100644 tgui/packages/tgui/interfaces/common/CommonTypes.ts rename tgui/packages/tgui/interfaces/common/{ComplexModal.jsx => ComplexModal.tsx} (60%) rename tgui/packages/tgui/interfaces/common/{FullscreenNotice.jsx => FullscreenNotice.tsx} (78%) rename tgui/packages/tgui/interfaces/common/{InterfaceLockNoticeBox.jsx => InterfaceLockNoticeBox.tsx} (89%) rename tgui/packages/tgui/interfaces/common/{LoginInfo.jsx => LoginInfo.tsx} (72%) rename tgui/packages/tgui/interfaces/common/{LoginScreen.jsx => LoginScreen.tsx} (86%) rename tgui/packages/tgui/interfaces/common/{Mining.jsx => Mining.tsx} (87%) rename tgui/packages/tgui/interfaces/common/{Overmap.jsx => Overmap.tsx} (92%) rename tgui/packages/tgui/interfaces/common/{PortableAtmos.jsx => PortableAtmos.tsx} (88%) rename tgui/packages/tgui/interfaces/common/{RankIcon.jsx => RankIcon.tsx} (97%) rename tgui/packages/tgui/interfaces/common/{TemporaryNotice.jsx => TemporaryNotice.tsx} (77%) rename tgui/packages/tgui/interfaces/pda/{pda_atmos_scan.jsx => pda_atmos_scan.tsx} (81%) rename tgui/packages/tgui/interfaces/pda/{pda_janitor.jsx => pda_janitor.tsx} (71%) rename tgui/packages/tgui/interfaces/pda/{pda_main_menu.jsx => pda_main_menu.tsx} (71%) rename tgui/packages/tgui/interfaces/pda/{pda_manifest.jsx => pda_manifest.tsx} (100%) rename tgui/packages/tgui/interfaces/pda/{pda_medical.jsx => pda_medical.tsx} (79%) rename tgui/packages/tgui/interfaces/pda/{pda_messenger.jsx => pda_messenger.tsx} (75%) rename tgui/packages/tgui/interfaces/pda/{pda_news.jsx => pda_news.tsx} (73%) rename tgui/packages/tgui/interfaces/pda/{pda_notekeeper.jsx => pda_notekeeper.tsx} (97%) rename tgui/packages/tgui/interfaces/pda/{pda_power.jsx => pda_power.tsx} (100%) rename tgui/packages/tgui/interfaces/pda/{pda_security.jsx => pda_security.tsx} (81%) rename tgui/packages/tgui/interfaces/pda/{pda_signaller.jsx => pda_signaller.tsx} (100%) rename tgui/packages/tgui/interfaces/pda/{pda_status_display.jsx => pda_status_display.tsx} (87%) rename tgui/packages/tgui/interfaces/pda/{pda_supply.jsx => pda_supply.tsx} (76%) create mode 100644 tgui/packages/tgui/interfaces/pda/pda_types.ts rename tgui/packages/tgui/layouts/{Pane.jsx => Pane.tsx} (64%) diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 9cc7e92cb5..d091eb4e1b 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -1022,12 +1022,11 @@ var/list/gamemode_cache = list() if("asset_cdn_url") config.asset_cdn_url = value -//ChompEDIT - these belong here if("allow_robot_recolor") - config.allow_robot_recolor = 1 + config.allow_robot_recolor = TRUE + if("allow_simple_mob_recolor") - config.allow_simple_mob_recolor = 1 -//ChompEDIT End + config.allow_simple_mob_recolor = TRUE else log_misc("Unknown setting in configuration: '[name]'") @@ -1097,12 +1096,6 @@ var/list/gamemode_cache = list() if("loadout_whitelist") config.loadout_whitelist = text2num(value) -/* //ChompEDIT - wrong place - if("allow_robot_recolor") - config.allow_robot_recolor = 1 - if("allow_simple_mob_recolor") - config.allow_simple_mob_recolor = 1 -*/ else log_misc("Unknown setting in configuration: '[name]'") diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm index 64287bee1b..d7323067eb 100644 --- a/code/datums/datacore.dm +++ b/code/datums/datacore.dm @@ -488,7 +488,6 @@ var/global/list/PDA_Manifest = list() R.fields["ma_crim"] = "None" R.fields["ma_crim_d"] = "No major crime convictions." R.fields["notes"] = "No notes." - R.fields["notes"] = "No notes." if(hidden) hidden_security += R else diff --git a/code/game/machinery/adv_med.dm b/code/game/machinery/adv_med.dm index b73df5ca3a..87d4146259 100644 --- a/code/game/machinery/adv_med.dm +++ b/code/game/machinery/adv_med.dm @@ -205,6 +205,11 @@ occupantData["bodyTempF"] = (((H.bodytemperature-T0C) * 1.8) + 32) occupantData["hasBorer"] = H.has_brain_worms() + occupantData["colourblind"] = null + for(var/datum/modifier/M in H.modifiers) + if(!isnull(M.wire_colors_replace)) + occupantData["colourblind"] = LAZYLEN(M.wire_colors_replace) + break var/bloodData[0] if(H.vessel) diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index aa4ebb65d7..23c50888b6 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -86,7 +86,7 @@ /obj/machinery/computer/cryopod/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, "CryoStorageVr", storage_name) // VOREStation Edit - Use our own template for our custom data + ui = new(user, src, "CryoStorage", storage_name) // VOREStation Edit - Use our own template for our custom data ui.open() /obj/machinery/computer/cryopod/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) diff --git a/code/modules/mob/living/bot/cleanbot.dm b/code/modules/mob/living/bot/cleanbot.dm index 410d11baa1..945968e70f 100644 --- a/code/modules/mob/living/bot/cleanbot.dm +++ b/code/modules/mob/living/bot/cleanbot.dm @@ -16,6 +16,7 @@ var/cleaning = 0 var/wet_floors = 0 var/spray_blood = 0 + var/blood = 1 var/list/target_types = list() /mob/living/bot/cleanbot/New() @@ -139,7 +140,8 @@ if(prob(20)) custom_emote(2, "begins to clean up \the [loc]") if(do_after(src, cleantime * cTimeMult)) - clean_blood() + if(blood) + clean_blood() if(istype(loc, /turf/simulated)) var/turf/simulated/T = loc T.dirt = 0 @@ -187,6 +189,7 @@ data["on"] = on data["open"] = open data["locked"] = locked + data["blood"] = blood data["patrol"] = will_patrol data["vocal"] = vocal @@ -208,6 +211,9 @@ else turn_on() . = TRUE + if("blood") + blood = !blood + . = TRUE if("patrol") will_patrol = !will_patrol patrol_path = null diff --git a/code/modules/mob/living/bot/edCLNbot.dm b/code/modules/mob/living/bot/edCLNbot.dm index 3a8265f352..0d976fef89 100644 --- a/code/modules/mob/living/bot/edCLNbot.dm +++ b/code/modules/mob/living/bot/edCLNbot.dm @@ -213,4 +213,4 @@ user.drop_item() qdel(W) user.drop_from_inventory(src) - qdel(src) \ No newline at end of file + qdel(src) diff --git a/code/modules/mob/living/silicon/robot/drone/drone_console.dm b/code/modules/mob/living/silicon/robot/drone/drone_console.dm index 2b8d37c7ae..fd092f3003 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_console.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_console.dm @@ -42,7 +42,7 @@ //VOREStation Edit - multiz lol if(D.foreign_droid) continue - + drones.Add(list(list( "name" = D.real_name, "active" = D.stat != 2, @@ -56,7 +56,10 @@ data["fabricator"] = dronefab data["fabPower"] = dronefab?.produce_drones - data["areas"] = GLOB.tagger_locations + var/list/areas = list() + for(var/area in GLOB.tagger_locations) + areas += area + data["areas"] = areas data["selected_area"] = "[drone_call_area]" return data diff --git a/code/modules/pda/cart_apps.dm b/code/modules/pda/cart_apps.dm index 857b7bbc04..74086c27b5 100644 --- a/code/modules/pda/cart_apps.dm +++ b/code/modules/pda/cart_apps.dm @@ -229,7 +229,7 @@ for(var/datum/supply_order/SO as anything in SSsupply.shoppinglist) supplyOrderCount++ - supplyOrderData[++supplyOrderData.len] = list("Number" = SO.ordernum, "Name" = html_encode(SO.object.name), "ApprovedBy" = SO.ordered_by, "Comment" = html_encode(SO.comment)) + supplyOrderData[++supplyOrderData.len] = list("Number" = SO.ordernum, "Name" = html_encode(SO.object.name), "ApprovedBy" = SO.approved_by, "Comment" = html_encode(SO.comment)) supplyData["approved"] = supplyOrderData supplyData["approved_count"] = supplyOrderCount diff --git a/code/modules/pda/core_apps.dm b/code/modules/pda/core_apps.dm index 94c30cea65..6e6ab50076 100644 --- a/code/modules/pda/core_apps.dm +++ b/code/modules/pda/core_apps.dm @@ -314,7 +314,9 @@ // Compile all the newscasts for(var/datum/feed_channel/channel in news_network.network_channels) if(!channel.censored) + var/index = 0 for(var/datum/feed_message/FM in channel.messages) + index++ var/body = replacetext(FM.body, "\n", "
") news[++news.len] = list( "channel" = channel.channel_name, @@ -324,7 +326,8 @@ "time_stamp" = FM.time_stamp, "has_image" = (FM.img != null), "caption" = FM.caption, - "time" = FM.post_time + "time" = FM.post_time, + "index" = index ) // Cut out all but the youngest three diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index 761a657d59..2762470488 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -804,7 +804,7 @@ GLOBAL_LIST_EMPTY(apcs) "emagged" = emagged, "isOperating" = operating, "externalPower" = main_status, - "powerCellStatus" = cell ? cell.percent() : null, + "powerCellStatus" = cell ? cell.percent() : 0, "chargeMode" = chargemode, "chargingStatus" = charging, "totalLoad" = round(lastused_total), diff --git a/code/modules/reagents/machinery/dispenser/dispenser2.dm b/code/modules/reagents/machinery/dispenser/dispenser2.dm index 43a19e7ac0..be5090e2e7 100644 --- a/code/modules/reagents/machinery/dispenser/dispenser2.dm +++ b/code/modules/reagents/machinery/dispenser/dispenser2.dm @@ -152,7 +152,7 @@ var/chemicals[0] for(var/label in cartridges) var/obj/item/weapon/reagent_containers/chem_disp_cartridge/C = cartridges[label] - chemicals.Add(list(list("title" = label, "id" = label, "amount" = C.reagents.total_volume))) // list in a list because Byond merges the first list... + chemicals.Add(list(list("name" = label, "id" = label, "volume" = C.reagents.total_volume))) // list in a list because Byond merges the first list... data["chemicals"] = chemicals return data diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm index 5c5b3d2d23..4a43d58597 100644 --- a/code/modules/recycling/disposal.dm +++ b/code/modules/recycling/disposal.dm @@ -1289,7 +1289,7 @@ /obj/structure/disposalpipe/tagger/New() . = ..() dpdir = dir | turn(dir, 180) - if(sort_tag) GLOB.tagger_locations |= sort_tag + if(sort_tag) GLOB.tagger_locations |= list("[sort_tag]" = get_z(src)) updatename() updatedesc() update() @@ -1355,7 +1355,7 @@ /obj/structure/disposalpipe/sortjunction/New() . = ..() - if(sortType) GLOB.tagger_locations |= sortType + if(sortType) GLOB.tagger_locations |= list("[sortType]" = get_z(src)) updatedir() updatename() diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm index 25b0154603..fb23a0326e 100755 --- a/code/modules/recycling/sortingmachinery.dm +++ b/code/modules/recycling/sortingmachinery.dm @@ -348,11 +348,23 @@ ui = new(user, src, "DestinationTagger", name) ui.open() +/obj/item/device/destTagger/tgui_static_data(mob/user) + var/list/data = ..() + var/list/taggers = list() + var/list/tagger_levels = list() + for(var/tag in GLOB.tagger_locations) + var/z_level = GLOB.tagger_locations[tag] + taggers += list(list("tag" = tag, "level" = z_level)) + tagger_levels += list(list("z" = z_level, "location" = using_map.get_zlevel_name(z_level))) + data["taggerLevels"] = tagger_levels + data["taggerLocs"] = taggers + + return data + /obj/item/device/destTagger/tgui_data(mob/user, datum/tgui/ui) var/list/data = ..() data["currTag"] = currTag - data["taggerLocs"] = GLOB.tagger_locations return data diff --git a/code/modules/shuttles/escape_pods.dm b/code/modules/shuttles/escape_pods.dm index 7490887c20..aa966b582a 100644 --- a/code/modules/shuttles/escape_pods.dm +++ b/code/modules/shuttles/escape_pods.dm @@ -55,9 +55,9 @@ . = list( "docking_status" = docking_program.get_docking_status(), "override_enabled" = docking_program.override_enabled, - "exterior_status" = docking_program.memory["door_status"], - "can_force" = pod.can_force() || (emergency_shuttle.departed && pod.can_launch()), //allow players to manually launch ahead of time if the shuttle leaves - "armed" = pod.arming_controller.armed, + "exterior_status" = docking_program.memory["door_status"], // TGUI DATA fails silently when there's no linked pod, leading to UI crashes + "can_force" = pod?.can_force() || (emergency_shuttle.departed && pod?.can_launch()), //allow players to manually launch ahead of time if the shuttle leaves + "armed" = pod?.arming_controller.armed, "internalTemplateName" = "EscapePodConsole", ) diff --git a/code/modules/tgui/modules/appearance_changer.dm b/code/modules/tgui/modules/appearance_changer.dm index e55296c902..9eeb4de664 100644 --- a/code/modules/tgui/modules/appearance_changer.dm +++ b/code/modules/tgui/modules/appearance_changer.dm @@ -1,4 +1,3 @@ - /datum/tgui_module/appearance_changer name = "Appearance Editor" tgui_id = "AppearanceChanger" @@ -63,6 +62,9 @@ cam_background.del_on_map_removal = FALSE update_active_camera_screen() + if(customize_usr) + if(ishuman(usr)) + H = usr owner = H if(owner) owner.AddComponent(/datum/component/recursive_move) @@ -184,8 +186,8 @@ instance = null if(!istype(instance) && !params["clear"]) return FALSE - owner.ear_style = instance - owner.update_hair() + target.ear_style = instance + target.update_hair() update_dna() changed_hook(APPEARANCECHANGER_CHANGED_HAIRSTYLE) return TRUE @@ -197,7 +199,7 @@ target.g_ears = hex2num(copytext(new_hair, 4, 6)) target.b_ears = hex2num(copytext(new_hair, 6, 8)) update_dna() - owner.update_hair() + target.update_hair() changed_hook(APPEARANCECHANGER_CHANGED_HAIRCOLOR) return 1 if("ears2_color") @@ -208,7 +210,7 @@ target.g_ears2 = hex2num(copytext(new_hair, 4, 6)) target.b_ears2 = hex2num(copytext(new_hair, 6, 8)) update_dna() - owner.update_hair() + target.update_hair() changed_hook(APPEARANCECHANGER_CHANGED_HAIRCOLOR) return 1 if("tail") @@ -218,8 +220,8 @@ instance = null if(!istype(instance) && !params["clear"]) return FALSE - owner.tail_style = instance - owner.update_tail_showing() + target.tail_style = instance + target.update_tail_showing() update_dna() changed_hook(APPEARANCECHANGER_CHANGED_HAIRSTYLE) return TRUE @@ -231,7 +233,7 @@ target.g_tail = hex2num(copytext(new_hair, 4, 6)) target.b_tail = hex2num(copytext(new_hair, 6, 8)) update_dna() - owner.update_tail_showing() + target.update_tail_showing() changed_hook(APPEARANCECHANGER_CHANGED_HAIRCOLOR) return 1 if("tail2_color") @@ -242,7 +244,7 @@ target.g_tail2 = hex2num(copytext(new_hair, 4, 6)) target.b_tail2 = hex2num(copytext(new_hair, 6, 8)) update_dna() - owner.update_tail_showing() + target.update_tail_showing() changed_hook(APPEARANCECHANGER_CHANGED_HAIRCOLOR) return 1 if("wing") @@ -252,8 +254,8 @@ instance = null if(!istype(instance) && !params["clear"]) return FALSE - owner.wing_style = instance - owner.update_wing_showing() + target.wing_style = instance + target.update_wing_showing() update_dna() changed_hook(APPEARANCECHANGER_CHANGED_HAIRSTYLE) return TRUE @@ -265,7 +267,7 @@ target.g_wing = hex2num(copytext(new_hair, 4, 6)) target.b_wing = hex2num(copytext(new_hair, 6, 8)) update_dna() - owner.update_wing_showing() + target.update_wing_showing() changed_hook(APPEARANCECHANGER_CHANGED_HAIRCOLOR) return 1 if("wing2_color") @@ -276,7 +278,7 @@ target.g_wing2 = hex2num(copytext(new_hair, 4, 6)) target.b_wing2 = hex2num(copytext(new_hair, 6, 8)) update_dna() - owner.update_wing_showing() + target.update_wing_showing() changed_hook(APPEARANCECHANGER_CHANGED_HAIRCOLOR) return 1 if("marking") diff --git a/code/modules/virus2/diseasesplicer.dm b/code/modules/virus2/diseasesplicer.dm index afc875a76e..cdb7948bdd 100644 --- a/code/modules/virus2/diseasesplicer.dm +++ b/code/modules/virus2/diseasesplicer.dm @@ -82,7 +82,7 @@ if(dish.growth >= 50) var/list/effects[0] for (var/datum/disease2/effectholder/e in dish.virus2.effects) - effects.Add(list(list("name" = (dish.analysed ? e.effect.name : "Unknown"), "stage" = (e.stage), "reference" = "\ref[e]"))) + effects.Add(list(list("name" = (dish.analysed ? e.effect.name : "Unknown"), "stage" = (e.stage), "reference" = "\ref[e]"), "badness" = e.effect.badness)) data["effects"] = effects else data["info"] = "Insufficient cell growth for gene splicing." @@ -191,4 +191,3 @@ if("disk") burning = 10 . = TRUE - diff --git a/code/modules/xenobio2/machinery/injector_computer.dm b/code/modules/xenobio2/machinery/injector_computer.dm index 196543a256..e4ac65403a 100644 --- a/code/modules/xenobio2/machinery/injector_computer.dm +++ b/code/modules/xenobio2/machinery/injector_computer.dm @@ -46,6 +46,7 @@ ..() +//Is missing a tgui interface? /obj/machinery/computer/xenobio2/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) if(!user) return diff --git a/tgui/packages/common/type-utils.ts b/tgui/packages/common/type-utils.ts new file mode 100644 index 0000000000..a73c0c1d59 --- /dev/null +++ b/tgui/packages/common/type-utils.ts @@ -0,0 +1,41 @@ +/** + * Helps visualize highly complex ui data on the fly. + * @example + * ```tsx + * const { data } = useBackend(); + * logger.log(getShallowTypes(data)); + * ``` + */ +export function getShallowTypes( + data: Record, +): Record { + const output = {}; + + for (const key in data) { + if (Array.isArray(data[key])) { + const arr: any[] = data[key]; + + // Return the first array item if it exists + if (data[key].length > 0) { + output[key] = arr[0]; + continue; + } + + output[key] = 'emptyarray'; + } else if (typeof data[key] === 'object' && data[key] !== null) { + // Please inspect it further and make a new type for it + output[key] = 'object (inspect) || Record'; + } else if (typeof data[key] === 'number') { + const num = Number(data[key]); + + // 0 and 1 could be booleans from byond + if (num === 1 || num === 0) { + output[key] = `${num}, BooleanLike?`; + continue; + } + output[key] = data[key]; + } + } + + return output; +} diff --git a/tgui/packages/tgui/backend.ts b/tgui/packages/tgui/backend.ts index 4af2e7418c..9d10d3f41f 100644 --- a/tgui/packages/tgui/backend.ts +++ b/tgui/packages/tgui/backend.ts @@ -262,6 +262,8 @@ type BackendState = { status: number; interface: string; refreshing: boolean; + map: string; // Vorestation Add + mapZLevel: number; // Vorestation Add window: { key: string; size: [number, number]; diff --git a/tgui/packages/tgui/components/Collapsible.tsx b/tgui/packages/tgui/components/Collapsible.tsx index b470ed5ce6..8f53e7d92b 100644 --- a/tgui/packages/tgui/components/Collapsible.tsx +++ b/tgui/packages/tgui/components/Collapsible.tsx @@ -13,11 +13,12 @@ type Props = Partial<{ buttons: ReactNode; open: boolean; title: ReactNode; + child_mt: number; // Vorestation Add }> & BoxProps; export function Collapsible(props: Props) { - const { children, color, title, buttons, ...rest } = props; + const { children, color, title, buttons, child_mt = 1, ...rest } = props; const [open, setOpen] = useState(props.open); return ( @@ -38,7 +39,7 @@ export function Collapsible(props: Props) {
{buttons}
)} - {open && {children}} + {open && {children}} ); } diff --git a/tgui/packages/tgui/components/NoticeBox.tsx b/tgui/packages/tgui/components/NoticeBox.tsx index db4f821d6b..73abc74322 100644 --- a/tgui/packages/tgui/components/NoticeBox.tsx +++ b/tgui/packages/tgui/components/NoticeBox.tsx @@ -11,7 +11,7 @@ import { Box, BoxProps } from './Box'; type Props = ExclusiveProps & BoxProps; /** You MUST use only one or none */ -type NoticeType = 'info' | 'success' | 'danger'; +type NoticeType = 'info' | 'success' | 'warning' | 'danger'; type None = { [K in NoticeType]?: undefined; @@ -25,12 +25,15 @@ type ExclusiveProps = | (Omit & { success: boolean; }) + | (Omit & { + warning: boolean; + }) | (Omit & { danger: boolean; }); export function NoticeBox(props: Props) { - const { className, color, info, success, danger, ...rest } = props; + const { className, color, info, success, danger, warning, ...rest } = props; return ( void; rightSlot: ReactNode; @@ -57,6 +58,7 @@ const Tab = (props: TabProps) => { selected, color, icon, + iconSpin, // Vorestation Add leftSlot, rightSlot, children, @@ -87,7 +89,7 @@ const Tab = (props: TabProps) => { {(canRender(leftSlot) &&
{leftSlot}
) || (!!icon && (
- +
))}
{children}
diff --git a/tgui/packages/tgui/interfaces/AICard.jsx b/tgui/packages/tgui/interfaces/AICard.tsx similarity index 86% rename from tgui/packages/tgui/interfaces/AICard.jsx rename to tgui/packages/tgui/interfaces/AICard.tsx index 1c2125e5d7..2d4dc59ad7 100644 --- a/tgui/packages/tgui/interfaces/AICard.jsx +++ b/tgui/packages/tgui/interfaces/AICard.tsx @@ -1,11 +1,26 @@ +import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; import { Box, Button, LabeledList, ProgressBar, Section } from '../components'; import { Window } from '../layouts'; +type Data = { + name: string; + has_ai: BooleanLike; + integrity: number; + backup_capacitor: number; + flushing: BooleanLike; + has_laws: BooleanLike; + laws: string[]; + wireless: BooleanLike; + radio: BooleanLike; +}; + export const AICard = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { + name, has_ai, integrity, backup_capacitor, @@ -16,7 +31,7 @@ export const AICard = (props) => { radio, } = data; - if (has_ai === 0) { + if (!has_ai) { return ( @@ -29,7 +44,7 @@ export const AICard = (props) => { ); } else { - let integrityColor = null; // Handles changing color of the integrity bar + let integrityColor: string | undefined; // Handles changing color of the integrity bar if (integrity >= 75) { integrityColor = 'green'; } else if (integrity >= 25) { @@ -38,7 +53,7 @@ export const AICard = (props) => { integrityColor = 'red'; } - let powerColor = null; + let powerColor: string | undefined; if (backup_capacitor >= 75) { powerColor = 'green'; } @@ -52,7 +67,7 @@ export const AICard = (props) => {
- +

{name}

@@ -77,7 +92,7 @@ export const AICard = (props) => { {(!!has_laws && ( {laws.map((value, key) => ( - + {value} ))} diff --git a/tgui/packages/tgui/interfaces/APC.jsx b/tgui/packages/tgui/interfaces/APC.tsx similarity index 67% rename from tgui/packages/tgui/interfaces/APC.jsx rename to tgui/packages/tgui/interfaces/APC.tsx index 032e5fb8bb..9df33fc68a 100644 --- a/tgui/packages/tgui/interfaces/APC.jsx +++ b/tgui/packages/tgui/interfaces/APC.tsx @@ -1,3 +1,5 @@ +import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; import { Box, @@ -12,14 +14,46 @@ import { Window } from '../layouts'; import { FullscreenNotice } from './common/FullscreenNotice'; import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; +type Data = { + gridCheck: BooleanLike; + failTime: number; + locked: BooleanLike; + normallyLocked: BooleanLike; + siliconUser: BooleanLike; + externalPower; + chargingStatus; + powerChannels: { + title: string; + powerLoad: number; + status: number; + topicParams: { + auto: Record; + on: Record; + off: Record; + }[]; + }; + powerCellStatus: number; + emagged: BooleanLike; + isOperating: BooleanLike; + chargeMode: BooleanLike; + totalCharging: number; + totalLoad: number; + coverLocked: BooleanLike; + nightshiftLights: BooleanLike; + nightshiftSetting: number; + emergencyLights: boolean; +}; + export const APC = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); + + const { gridCheck, failTime } = data; let body = ; - if (data.gridCheck) { + if (gridCheck) { body = ; - } else if (data.failTime) { + } else if (failTime) { body = ; } @@ -30,7 +64,13 @@ export const APC = (props) => { ); }; -const powerStatusMap = { +type powerStatus = { + color: string; + externalPowerText: string; + chargingText: string; +}; + +const powerStatusMap: Record = { 2: { color: 'good', externalPowerText: 'External Power', @@ -48,7 +88,10 @@ const powerStatusMap = { }, }; -const malfMap = { +const malfMap: Record< + number, + { icon: string; content: string; action: string } +> = { 1: { icon: 'terminal', content: 'Override Programming', @@ -72,21 +115,38 @@ const malfMap = { }; const ApcContent = (props) => { - const { act, data } = useBackend(); - const locked = data.locked && !data.siliconUser; - const normallyLocked = data.normallyLocked; - const externalPowerStatus = - powerStatusMap[data.externalPower] || powerStatusMap[0]; - const chargingStatus = - powerStatusMap[data.chargingStatus] || powerStatusMap[0]; - const channelArray = data.powerChannels || []; + const { act, data } = useBackend(); + + const { + locked, + siliconUser, + externalPower, + chargingStatus, + powerChannels, + powerCellStatus, + emagged, + isOperating, + chargeMode, + totalCharging, + totalLoad, + coverLocked, + nightshiftSetting, + emergencyLights, + } = data; + + const is_locked: BooleanLike = locked && !siliconUser; + const externalPowerStatus: powerStatus = + powerStatusMap[externalPower] || powerStatusMap[0]; + const chargingPowerStatus: powerStatus = + powerStatusMap[chargingStatus] || powerStatusMap[0]; + const channelArray: any = powerChannels || []; // const malfStatus = malfMap[data.malfStatus] || null; - const adjustedCellChange = data.powerCellStatus / 100; + const adjustedCellChange: number = powerCellStatus / 100; return ( <> @@ -103,13 +163,13 @@ const ApcContent = (props) => { color={externalPowerStatus.color} buttons={ } > @@ -120,19 +180,19 @@ const ApcContent = (props) => { act('charge')} > - {data.chargeMode ? 'Auto' : 'Off'} + {chargeMode ? 'Auto' : 'Off'} } > - [ {chargingStatus.chargingText} ] + [ {chargingPowerStatus.chargingText} ]
@@ -156,26 +216,26 @@ const ApcContent = (props) => { } /> @@ -228,7 +288,7 @@ const ApcContent = (props) => { <> } /> @@ -299,7 +359,9 @@ const GridCheck = (props) => { }; const ApcFailure = (props) => { - const { data, act } = useBackend(); + const { data, act } = useBackend(); + + const { locked, siliconUser, failTime } = data; let rebootOptions = ( ); - if (data.locked && !data.siliconUser) { + if (locked && !siliconUser) { rebootOptions = Swipe an ID card for manual reboot.; } @@ -321,7 +383,7 @@ const ApcFailure = (props) => { I/O regulators malfunction detected! Waiting for system reboot...
- Automatic reboot in {data.failTime} seconds... + Automatic reboot in {failTime} seconds... {rebootOptions} ); diff --git a/tgui/packages/tgui/interfaces/AccountsTerminal.jsx b/tgui/packages/tgui/interfaces/AccountsTerminal.tsx similarity index 82% rename from tgui/packages/tgui/interfaces/AccountsTerminal.jsx rename to tgui/packages/tgui/interfaces/AccountsTerminal.tsx index 9831a56063..ba2fd43bb5 100644 --- a/tgui/packages/tgui/interfaces/AccountsTerminal.jsx +++ b/tgui/packages/tgui/interfaces/AccountsTerminal.tsx @@ -1,3 +1,5 @@ +import { BooleanLike } from 'common/react'; + import { useBackend, useSharedState } from '../backend'; import { Box, @@ -10,8 +12,36 @@ import { } from '../components'; import { Window } from '../layouts'; +type Data = { + id_inserted: BooleanLike; + id_card: string; + access_level: number; + machine_id: string; + creating_new_account: BooleanLike; + detailed_account_view: boolean; + station_account_number: number; + account_number: number | null; + owner_name: string | null; + money: number | null; + suspended: BooleanLike; + transactions: { + date: string; + time: string; + target_name: string; + purpose: string; + amount: number; + source_terminal: string; + }[]; + accounts: { + account_number: number; + owner_name: string; + suspended: string; + account_index: number; + }[]; +}; + export const AccountsTerminal = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { id_inserted, id_card, access_level, machine_id } = data; @@ -41,7 +71,7 @@ export const AccountsTerminal = (props) => { }; const AccountTerminalContent = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { creating_new_account, detailed_account_view } = data; @@ -56,18 +86,14 @@ const AccountTerminalContent = (props) => { Home act('create_account')} > New Account {!creating_new_account ? ( - act('print')} - > + act('print')}> Print ) : ( @@ -81,13 +107,13 @@ const AccountTerminalContent = (props) => { }; const NewAccountView = (props) => { - const { act } = useBackend(); + const { act } = useBackend(); const [holder, setHolder] = useSharedState('holder', ''); const [newMoney, setMoney] = useSharedState('money', ''); return ( -
+
setHolder(val)} /> @@ -115,7 +141,7 @@ const NewAccountView = (props) => { }; const DetailedView = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { access_level, @@ -130,7 +156,6 @@ const DetailedView = (props) => { return (
{ {suspended ? 'SUSPENDED' : 'Active'} -
+
{
{access_level >= 2 && ( -
+
@@ -178,7 +203,7 @@ const DetailedView = (props) => {
)} -
+
Timestamp @@ -205,18 +230,18 @@ const DetailedView = (props) => { }; const ListView = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { accounts } = data; return ( -
+
{(accounts.length && ( {accounts.map((acc) => ( } > - {data.power.main ? 'Online' : 'Offline'}{' '} - {((!data.wires.main_1 || !data.wires.main_2) && - '[Wires have been cut!]') || - (data.power.main_timeleft > 0 && - `[${data.power.main_timeleft}s]`)} + {power.main ? 'Online' : 'Offline'}{' '} + {((!wires.main_1 || !wires.main_2) && '[Wires have been cut!]') || + (power.main_timeleft > 0 && `[${power.main_timeleft}s]`)} { buttons={ } > - {data.power.backup ? 'Online' : 'Offline'}{' '} - {((!data.wires.backup_1 || !data.wires.backup_2) && + {power.backup ? 'Online' : 'Offline'}{' '} + {((!wires.backup_1 || !wires.backup_2) && '[Wires have been cut!]') || - (data.power.backup_timeleft > 0 && - `[${data.power.backup_timeleft}s]`)} + (power.backup_timeleft > 0 && `[${power.backup_timeleft}s]`)} { <>
@@ -108,16 +152,16 @@ export const AiAirlock = (props) => { color="bad" buttons={ } > - {!data.wires.id_scanner && '[Wires have been cut!]'} + {!wires.id_scanner && '[Wires have been cut!]'} { color="bad" buttons={ } > - {!data.wires.bolts && '[Wires have been cut!]'} + {!wires.bolts && '[Wires have been cut!]'} act('light-toggle')} > - {data.lights ? 'Enabled' : 'Disabled'} + {lights ? 'Enabled' : 'Disabled'} } > - {!data.wires.lights && '[Wires have been cut!]'} + {!wires.lights && '[Wires have been cut!]'} act('safe-toggle')} > - {data.safe ? 'Enabled' : 'Disabled'} + {safe ? 'Enabled' : 'Disabled'} } > - {!data.wires.safe && '[Wires have been cut!]'} + {!wires.safe && '[Wires have been cut!]'} act('speed-toggle')} > - {data.speed ? 'Enabled' : 'Disabled'} + {speed ? 'Enabled' : 'Disabled'} } > - {!data.wires.timing && '[Wires have been cut!]'} + {!wires.timing && '[Wires have been cut!]'} { color="bad" buttons={ } > - {!!(data.locked || data.welded) && ( + {!!(locked || welded) && ( - [Door is {data.locked ? 'bolted' : ''} - {data.locked && data.welded ? ' and ' : ''} - {data.welded ? 'welded' : ''}!] + [Door is {locked ? 'bolted' : ''} + {locked && welded ? ' and ' : ''} + {welded ? 'welded' : ''}!] )} diff --git a/tgui/packages/tgui/interfaces/AiRestorer.jsx b/tgui/packages/tgui/interfaces/AiRestorer.tsx similarity index 87% rename from tgui/packages/tgui/interfaces/AiRestorer.jsx rename to tgui/packages/tgui/interfaces/AiRestorer.tsx index c6b4c81cca..7db847a58c 100644 --- a/tgui/packages/tgui/interfaces/AiRestorer.jsx +++ b/tgui/packages/tgui/interfaces/AiRestorer.tsx @@ -1,3 +1,5 @@ +import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; import { Box, @@ -9,6 +11,17 @@ import { } from '../components'; import { Window } from '../layouts'; +type Data = { + AI_present: boolean; + error: string | null; + name: string; + laws: string[]; + isDead: boolean; + restoring: BooleanLike; + health: number; + ejectable: boolean; +}; + export const AiRestorer = () => { return ( @@ -20,7 +33,7 @@ export const AiRestorer = () => { }; export const AiRestorerContent = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { AI_present, error, @@ -81,7 +94,7 @@ export const AiRestorerContent = (props) => { > Begin Reconstruction -
+
{laws.map((law) => ( {law} diff --git a/tgui/packages/tgui/interfaces/AiSupermatter.jsx b/tgui/packages/tgui/interfaces/AiSupermatter.tsx similarity index 87% rename from tgui/packages/tgui/interfaces/AiSupermatter.jsx rename to tgui/packages/tgui/interfaces/AiSupermatter.tsx index 0e8105c65c..bf46960446 100644 --- a/tgui/packages/tgui/interfaces/AiSupermatter.jsx +++ b/tgui/packages/tgui/interfaces/AiSupermatter.tsx @@ -1,13 +1,21 @@ +import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; import { Box, Icon, LabeledList, ProgressBar, Section } from '../components'; import { Window } from '../layouts'; import { FullscreenNotice } from './common/FullscreenNotice'; -export const AiSupermatter = (props) => { - const { data } = useBackend(); +type Data = { + detonating: BooleanLike; + integrity_percentage: number; + ambient_temp: number; + ambient_pressure: number; +}; - const { integrity_percentage, ambient_temp, ambient_pressure, detonating } = - data; +export const AiSupermatter = (props) => { + const { data } = useBackend(); + + const { detonating } = data; let body = ; if (detonating) { @@ -38,7 +46,7 @@ const AiSupermatterDetonation = (props) => ( ); const AiSupermatterContent = (props) => { - const { data } = useBackend(); + const { data } = useBackend(); const { integrity_percentage, ambient_temp, ambient_pressure } = data; diff --git a/tgui/packages/tgui/interfaces/AirAlarm.jsx b/tgui/packages/tgui/interfaces/AirAlarm.tsx similarity index 78% rename from tgui/packages/tgui/interfaces/AirAlarm.jsx rename to tgui/packages/tgui/interfaces/AirAlarm.tsx index 451c1c1b61..ebdbdaf331 100644 --- a/tgui/packages/tgui/interfaces/AirAlarm.jsx +++ b/tgui/packages/tgui/interfaces/AirAlarm.tsx @@ -1,4 +1,5 @@ import { toFixed } from 'common/math'; +import { BooleanLike } from 'common/react'; import { Fragment, useState } from 'react'; import { useBackend } from '../backend'; @@ -6,10 +7,57 @@ import { Box, Button, LabeledList, Section } from '../components'; import { getGasColor, getGasLabel } from '../constants'; import { Window } from '../layouts'; import { Scrubber, Vent } from './common/AtmosControls'; +import { single_scrubber, single_vent } from './common/CommonTypes'; import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; +type Data = { + locked: BooleanLike; + siliconUser: BooleanLike; + remoteUser: BooleanLike; + environment_data: { + name: string; + value: number; + unit: string; + danger_level: number; + }[]; + danger_level: number; + target_temperature: string; + rcon: number; + atmos_alarm: BooleanLike; + fire_alarm: BooleanLike; + emagged: BooleanLike; // Seems unused + mode: number; + thresholds: thresholds[]; + scrubbers: single_scrubber[]; + vents: single_vent[]; + modes: { + name: string; + mode: number; + selected: BooleanLike; + danger: BooleanLike; + }[]; +}; + +type thresholds = { + name: string; + settings: { + env: string; + val: number; + selected: { + oxygen: number[]; + carbon_dioxide: number; + phoron: number; + other: number; + pressure: number; + temperature: number; + }; + }[]; +}; + export const AirAlarm = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); + + const { locked, siliconUser, remoteUser } = data; const [screen, setScreen] = useState(''); @@ -17,14 +65,14 @@ export const AirAlarm = (props) => { setScreen(value); } - const locked = data.locked && !data.siliconUser && !data.remoteUser; + const is_locked: BooleanLike = locked && !siliconUser && !remoteUser; return ( - {!locked && ( + {!is_locked && ( )} @@ -33,24 +81,28 @@ export const AirAlarm = (props) => { }; const AirAlarmStatus = (props) => { - const { data } = useBackend(); - const entries = (data.environment_data || []).filter( + const { data } = useBackend(); + + const { environment_data, atmos_alarm, fire_alarm, emagged } = data; + + const entries = (environment_data || []).filter( (entry) => entry.value >= 0.01, ); - const dangerMap = { - 0: { - color: 'good', - localStatusText: 'Optimal', - }, - 1: { - color: 'average', - localStatusText: 'Caution', - }, - 2: { - color: 'bad', - localStatusText: 'Danger (Internals Required)', - }, - }; + const dangerMap: Record = + { + 0: { + color: 'good', + localStatusText: 'Optimal', + }, + 1: { + color: 'average', + localStatusText: 'Caution', + }, + 2: { + color: 'bad', + localStatusText: 'Danger (Internals Required)', + }, + }; const localStatus = dangerMap[data.danger_level] || dangerMap[0]; return (
@@ -75,10 +127,10 @@ const AirAlarmStatus = (props) => { - {(data.atmos_alarm && 'Atmosphere Alarm') || - (data.fire_alarm && 'Fire Alarm') || + {(atmos_alarm && 'Atmosphere Alarm') || + (fire_alarm && 'Fire Alarm') || 'Nominal'} @@ -87,7 +139,7 @@ const AirAlarmStatus = (props) => { Cannot obtain air sample for analysis. )} - {!!data.emagged && ( + {!!emagged && ( Safety measures offline. Device may exhibit abnormal behavior. @@ -98,7 +150,7 @@ const AirAlarmStatus = (props) => { }; const AirAlarmUnlockedControl = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { target_temperature, rcon } = data; return (
@@ -179,7 +231,7 @@ const AirAlarmControl = (props) => { // -------------------------------------------------------- const AirAlarmControlHome = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { mode, atmos_alarm } = data; return ( <> @@ -226,7 +278,7 @@ const AirAlarmControlHome = (props) => { // -------------------------------------------------------- const AirAlarmControlVents = (props) => { - const { data } = useBackend(); + const { data } = useBackend(); const { vents } = data; if (!vents || vents.length === 0) { return 'Nothing to show'; @@ -238,7 +290,7 @@ const AirAlarmControlVents = (props) => { // -------------------------------------------------------- const AirAlarmControlScrubbers = (props) => { - const { data } = useBackend(); + const { data } = useBackend(); const { scrubbers } = data; if (!scrubbers || scrubbers.length === 0) { return 'Nothing to show'; @@ -252,7 +304,7 @@ const AirAlarmControlScrubbers = (props) => { // -------------------------------------------------------- const AirAlarmControlModes = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { modes } = data; if (!modes || modes.length === 0) { return 'Nothing to show'; @@ -276,7 +328,7 @@ const AirAlarmControlModes = (props) => { // -------------------------------------------------------- const AirAlarmControlThresholds = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { thresholds } = data; return (
diff --git a/tgui/packages/tgui/interfaces/AlgaeFarm.jsx b/tgui/packages/tgui/interfaces/AlgaeFarm.tsx similarity index 88% rename from tgui/packages/tgui/interfaces/AlgaeFarm.jsx rename to tgui/packages/tgui/interfaces/AlgaeFarm.tsx index a8a219f82d..3774fdd5ce 100644 --- a/tgui/packages/tgui/interfaces/AlgaeFarm.jsx +++ b/tgui/packages/tgui/interfaces/AlgaeFarm.tsx @@ -12,8 +12,28 @@ import { } from '../components'; import { Window } from '../layouts'; +type Data = { + usePower: number; + materials: { + name: string; + display: string; + qty: number; + max: number; + percent: number; + }[]; + last_flow_rate: number; + last_power_draw: number; + inputDir: string; + outputDir: string; + input: gas; + output: gas; + errorText: string | null; +}; + +type gas = { pressure: number; name: string; percent: number; moles: number }; + export const AlgaeFarm = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { usePower, materials, @@ -31,7 +51,7 @@ export const AlgaeFarm = (props) => { {errorText && ( - + {errorText} diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger.jsx b/tgui/packages/tgui/interfaces/AppearanceChanger.jsx deleted file mode 100644 index 9a7d0fb83b..0000000000 --- a/tgui/packages/tgui/interfaces/AppearanceChanger.jsx +++ /dev/null @@ -1,523 +0,0 @@ -import { sortBy } from 'common/collections'; -import { capitalize, decodeHtmlEntities } from 'common/string'; -import { useState } from 'react'; - -import { useBackend } from '../backend'; -import { - Box, - Button, - ByondUi, - ColorBox, - Flex, - LabeledList, - Section, - Tabs, -} from '../components'; -import { Window } from '../layouts'; - -export const AppearanceChanger = (props) => { - const { act, config, data } = useBackend(); - - const { - name, - specimen, - gender, - gender_id, - hair_style, - facial_hair_style, - ear_style, - tail_style, - wing_style, - markings, - change_race, - change_gender, - change_eye_color, - change_skin_tone, - change_skin_color, - change_hair_color, - change_facial_hair_color, - change_hair, - change_facial_hair, - mapRef, - } = data; - - const { title } = config; - - const change_color = - change_eye_color || - change_skin_tone || - change_skin_color || - change_hair_color || - change_facial_hair_color; - - let firstAccesibleTab = -1; - if (change_race) { - firstAccesibleTab = 0; - } else if (change_gender) { - firstAccesibleTab = 1; - } else if (change_color) { - firstAccesibleTab = 2; - } else if (change_hair) { - firstAccesibleTab = 4; - } else if (change_facial_hair) { - firstAccesibleTab = 5; - } - - const [tabIndex, setTabIndex] = useState(firstAccesibleTab); - - return ( - - -
- - - - {name} - - {specimen} - - - {gender ? capitalize(gender) : 'Not Set'} - - - {gender_id ? capitalize(gender_id) : 'Not Set'} - - - {hair_style ? capitalize(hair_style) : 'Not Set'} - - - {facial_hair_style - ? capitalize(facial_hair_style) - : 'Not Set'} - - - {ear_style ? capitalize(ear_style) : 'Not Set'} - - - {tail_style ? capitalize(tail_style) : 'Not Set'} - - - {wing_style ? capitalize(wing_style) : 'Not Set'} - - - - - - - -
- - {change_race ? ( - setTabIndex(0)}> - Race - - ) : null} - {change_gender ? ( - setTabIndex(1)}> - Gender & Sex - - ) : null} - {change_color ? ( - setTabIndex(2)}> - Colors - - ) : null} - {change_hair ? ( - <> - setTabIndex(3)} - > - Hair - - setTabIndex(5)} - > - Ear - - setTabIndex(6)} - > - Tail - - setTabIndex(7)} - > - Wing - - setTabIndex(8)} - > - Markings - - - ) : null} - {change_facial_hair ? ( - setTabIndex(4)}> - Facial Hair - - ) : null} - - - {change_race && tabIndex === 0 ? : null} - {change_gender && tabIndex === 1 ? : null} - {change_color && tabIndex === 2 ? : null} - {change_hair && tabIndex === 3 ? : null} - {change_facial_hair && tabIndex === 4 ? ( - - ) : null} - {change_hair && tabIndex === 5 ? : null} - {change_hair && tabIndex === 6 ? : null} - {change_hair && tabIndex === 7 ? : null} - {change_hair && tabIndex === 8 ? : null} - -
-
- ); -}; - -const AppearanceChangerSpecies = (props) => { - const { act, data } = useBackend(); - const { species, specimen } = data; - - const sortedSpecies = sortBy((val) => val.specimen)(species || []); - - return ( -
- {sortedSpecies.map((spec) => ( - - ))} -
- ); -}; - -const AppearanceChangerGender = (props) => { - const { act, data } = useBackend(); - - const { gender, gender_id, genders, id_genders } = data; - - return ( -
- - - {genders.map((g) => ( - - ))} - - - {id_genders.map((g) => ( - - ))} - - -
- ); -}; - -const AppearanceChangerColors = (props) => { - const { act, data } = useBackend(); - - const { - change_eye_color, - change_skin_tone, - change_skin_color, - change_hair_color, - change_facial_hair_color, - eye_color, - skin_color, - hair_color, - facial_hair_color, - ears_color, - ears2_color, - tail_color, - tail2_color, - wing_color, - wing2_color, - } = data; - - return ( -
- {change_eye_color ? ( - - - - - ) : null} - {change_skin_tone ? ( - - - - ) : null} - {change_skin_color ? ( - - - - - ) : null} - {change_hair_color ? ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) : null} - {change_facial_hair_color ? ( - - - - - ) : null} -
- ); -}; - -const AppearanceChangerHair = (props) => { - const { act, data } = useBackend(); - - const { hair_style, hair_styles } = data; - - return ( -
- {hair_styles.map((hair) => ( - - ))} -
- ); -}; - -const AppearanceChangerFacialHair = (props) => { - const { act, data } = useBackend(); - - const { facial_hair_style, facial_hair_styles } = data; - - return ( -
- {facial_hair_styles.map((hair) => ( - - ))} -
- ); -}; - -const AppearanceChangerEars = (props) => { - const { act, data } = useBackend(); - - const { ear_style, ear_styles } = data; - - return ( -
- - {sortBy((e) => e.name.toLowerCase())(ear_styles).map((ear) => ( - - ))} -
- ); -}; - -const AppearanceChangerTails = (props) => { - const { act, data } = useBackend(); - - const { tail_style, tail_styles } = data; - - return ( -
- - {sortBy((e) => e.name.toLowerCase())(tail_styles).map((tail) => ( - - ))} -
- ); -}; - -const AppearanceChangerWings = (props) => { - const { act, data } = useBackend(); - - const { wing_style, wing_styles } = data; - - return ( -
- - {sortBy((e) => e.name.toLowerCase())(wing_styles).map((wing) => ( - - ))} -
- ); -}; - -const AppearanceChangerMarkings = (props) => { - const { act, data } = useBackend(); - - const { markings } = data; - - return ( -
- - - - - {markings.map((m) => ( - - - - - - - - ))} - -
- ); -}; diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx new file mode 100644 index 0000000000..66cfcbb886 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerBody.tsx @@ -0,0 +1,139 @@ +import { sortBy } from 'common/collections'; + +import { useBackend } from '../../backend'; +import { Button, LabeledList, Section } from '../../components'; +import { Data, species, styles } from './types'; + +export const AppearanceChangerSpecies = (props) => { + const { act, data } = useBackend(); + const { species, specimen } = data; + + const sortedSpecies = sortBy((val: species) => val.specimen)(species || []); + + return ( +
+ {sortedSpecies.map((spec) => ( + + ))} +
+ ); +}; + +export const AppearanceChangerGender = (props) => { + const { act, data } = useBackend(); + + const { gender, gender_id, genders, id_genders } = data; + + return ( +
+ + + {genders.map((g) => ( + + ))} + + + {id_genders.map((g) => ( + + ))} + + +
+ ); +}; + +export const AppearanceChangerEars = (props) => { + const { act, data } = useBackend(); + + const { ear_style, ear_styles } = data; + + return ( +
+ + {sortBy((e: styles) => e.name.toLowerCase())(ear_styles).map((ear) => ( + + ))} +
+ ); +}; + +export const AppearanceChangerTails = (props) => { + const { act, data } = useBackend(); + + const { tail_style, tail_styles } = data; + + return ( +
+ + {sortBy((e: styles) => e.name.toLowerCase())(tail_styles).map((tail) => ( + + ))} +
+ ); +}; + +export const AppearanceChangerWings = (props) => { + const { act, data } = useBackend(); + + const { wing_style, wing_styles } = data; + + return ( +
+ + {sortBy((e: styles) => e.name.toLowerCase())(wing_styles).map((wing) => ( + + ))} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerDetails.tsx b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerDetails.tsx new file mode 100644 index 0000000000..c5d2f403b7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerDetails.tsx @@ -0,0 +1,142 @@ +import { useBackend } from '../../backend'; +import { Box, Button, ColorBox, LabeledList, Section } from '../../components'; +import { Data } from './types'; + +export const AppearanceChangerColors = (props) => { + const { act, data } = useBackend(); + + const { + change_eye_color, + change_skin_tone, + change_skin_color, + change_hair_color, + change_facial_hair_color, + eye_color, + skin_color, + hair_color, + facial_hair_color, + ears_color, + ears2_color, + tail_color, + tail2_color, + wing_color, + wing2_color, + } = data; + + return ( +
+ {change_eye_color ? ( + + + + + ) : ( + '' + )} + {change_skin_tone ? ( + + + + ) : ( + '' + )} + {change_skin_color ? ( + + + + + ) : ( + '' + )} + {change_hair_color ? ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) : null} + {change_facial_hair_color ? ( + + + + + ) : null} +
+ ); +}; + +export const AppearanceChangerMarkings = (props) => { + const { act, data } = useBackend(); + + const { markings } = data; + + return ( +
+ + + + + {markings.map((m) => ( + + + + + + + + ))} + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerHairs.tsx b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerHairs.tsx new file mode 100644 index 0000000000..9e61a2c1a7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AppearanceChanger/AppearanceChangerHairs.tsx @@ -0,0 +1,45 @@ +import { useBackend } from '../../backend'; +import { Button, Section } from '../../components'; +import { Data } from './types'; + +export const AppearanceChangerHair = (props) => { + const { act, data } = useBackend(); + + const { hair_style, hair_styles } = data; + + return ( +
+ {hair_styles.map((hair) => ( + + ))} +
+ ); +}; + +export const AppearanceChangerFacialHair = (props) => { + const { act, data } = useBackend(); + + const { facial_hair_style, facial_hair_styles } = data; + + return ( +
+ {facial_hair_styles.map((hair) => ( + + ))} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/index.tsx b/tgui/packages/tgui/interfaces/AppearanceChanger/index.tsx new file mode 100644 index 0000000000..48935191fa --- /dev/null +++ b/tgui/packages/tgui/interfaces/AppearanceChanger/index.tsx @@ -0,0 +1,269 @@ +import { capitalize, decodeHtmlEntities } from 'common/string'; +import { useState } from 'react'; + +import { useBackend } from '../../backend'; +import { + Box, + ByondUi, + Flex, + LabeledList, + Section, + Tabs, +} from '../../components'; +import { Window } from '../../layouts'; +import { + AppearanceChangerEars, + AppearanceChangerGender, + AppearanceChangerSpecies, + AppearanceChangerTails, + AppearanceChangerWings, +} from './AppearanceChangerBody'; +import { + AppearanceChangerColors, + AppearanceChangerMarkings, +} from './AppearanceChangerDetails'; +import { + AppearanceChangerFacialHair, + AppearanceChangerHair, +} from './AppearanceChangerHairs'; +import { Data } from './types'; + +export const AppearanceChanger = (props) => { + const { act, config, data } = useBackend(); + + const { + name, + specimen, + gender, + gender_id, + hair_style, + facial_hair_style, + ear_style, + tail_style, + wing_style, + change_race, + change_gender, + change_eye_color, + change_skin_tone, + change_skin_color, + change_hair_color, + change_facial_hair_color, + change_hair, + change_facial_hair, + mapRef, + } = data; + + const { title } = config; + + const tab: React.JSX.Element[] = []; + + const change_color = + change_eye_color || + change_skin_tone || + change_skin_color || + change_hair_color || + change_facial_hair_color; + + const disabled = ; + + tab[-1] = ; + tab[0] = change_race ? ( + + ) : ( + + ); + tab[1] = change_gender ? ( + + ) : ( + + ); + tab[2] = change_color ? ( + + ) : ( + + ); + tab[3] = change_hair ? ( + + ) : ( + + ); + tab[4] = change_facial_hair ? ( + + ) : ( + + ); + tab[5] = change_hair ? ( + + ) : ( + + ); + tab[6] = change_hair ? ( + + ) : ( + + ); + tab[7] = change_hair ? ( + + ) : ( + + ); + tab[8] = change_hair ? ( + + ) : ( + + ); + + let firstAccesibleTab = -1; + if (change_race) { + firstAccesibleTab = 0; + } else if (change_gender) { + firstAccesibleTab = 1; + } else if (change_color) { + firstAccesibleTab = 2; + } else if (change_hair) { + firstAccesibleTab = 4; + } else if (change_facial_hair) { + firstAccesibleTab = 5; + } + + const [tabIndex, setTabIndex] = useState(firstAccesibleTab); + + return ( + + +
+ + + + {name} + + {specimen} + + + {gender ? capitalize(gender) : 'Not Set'} + + + {gender_id ? capitalize(gender_id) : 'Not Set'} + + + {hair_style ? capitalize(hair_style) : 'Not Set'} + + + {facial_hair_style + ? capitalize(facial_hair_style) + : 'Not Set'} + + + {ear_style ? capitalize(ear_style) : 'Not Set'} + + + {tail_style ? capitalize(tail_style) : 'Not Set'} + + + {wing_style ? capitalize(wing_style) : 'Not Set'} + + + + + + + +
+ + {change_race ? ( + setTabIndex(0)}> + Race + + ) : null} + {change_gender ? ( + setTabIndex(1)}> + Gender & Sex + + ) : null} + {change_color ? ( + setTabIndex(2)}> + Colors + + ) : null} + {change_hair ? ( + <> + setTabIndex(3)} + > + Hair + + setTabIndex(5)} + > + Ear + + setTabIndex(6)} + > + Tail + + setTabIndex(7)} + > + Wing + + setTabIndex(8)} + > + Markings + + + ) : null} + {change_facial_hair ? ( + setTabIndex(4)}> + Facial Hair + + ) : null} + + {tab[tabIndex]} +
+
+ ); +}; + +export const AppearanceChangerDefaultError = (props) => { + return Disabled; +}; diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger/types.ts b/tgui/packages/tgui/interfaces/AppearanceChanger/types.ts new file mode 100644 index 0000000000..4d62e13177 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AppearanceChanger/types.ts @@ -0,0 +1,53 @@ +import { BooleanLike } from 'common/react'; + +export type Data = { + name: string; + specimen: string; + species: species[]; + gender: string; + gender_id: string; + hair_style: string; + facial_hair_style: string; + ear_style: string; + ear_styles: styles[]; + wing_style: string; + wing_styles: styles[]; + tail_style: string; + tail_styles: styles[]; + markings: { marking_name: string; marking_color: string }[]; + change_race: BooleanLike; + change_gender: BooleanLike; + genders: genders; + id_genders: genders; + change_eye_color: BooleanLike; + change_skin_tone: BooleanLike; + change_skin_color: BooleanLike; + change_hair_color: BooleanLike; + change_facial_hair_color: BooleanLike; + change_hair: BooleanLike; + change_facial_hair: BooleanLike; + mapRef: string; + eye_color: string; + skin_color: string; + hair_color: string; + facial_hair_color: string; + ears_color: string; + ears2_color: string; + tail_color: string; + tail2_color: string; + wing_color: string; + wing2_color: string; + facial_hair_styles: { facialhairstyle: string }[]; + hair_styles: { hairstyle: string }[]; +}; + +type genders = { gender_name: string; gender_key: string }[]; + +export type styles = { + name: string; + instance: string; + color: boolean; + second_color: boolean; +}; + +export type species = { specimen: string }; diff --git a/tgui/packages/tgui/interfaces/ArcadeBattle.jsx b/tgui/packages/tgui/interfaces/ArcadeBattle.tsx similarity index 93% rename from tgui/packages/tgui/interfaces/ArcadeBattle.jsx rename to tgui/packages/tgui/interfaces/ArcadeBattle.tsx index 9ffead3baf..f1300c7338 100644 --- a/tgui/packages/tgui/interfaces/ArcadeBattle.jsx +++ b/tgui/packages/tgui/interfaces/ArcadeBattle.tsx @@ -1,3 +1,5 @@ +import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; import { Box, @@ -9,18 +11,27 @@ import { } from '../components'; import { Window } from '../layouts'; +type Data = { + name: string; + temp: string; + enemyAction: string; + enemyName: string; + playerHP: number; + playerMP: number; + enemyHP: number; + gameOver: BooleanLike; +}; + export const ArcadeBattle = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { - name, temp, enemyAction, enemyName, playerHP, playerMP, enemyHP, - enemyMP, gameOver, } = data; diff --git a/tgui/packages/tgui/interfaces/AreaScrubberControl.jsx b/tgui/packages/tgui/interfaces/AreaScrubberControl.tsx similarity index 88% rename from tgui/packages/tgui/interfaces/AreaScrubberControl.jsx rename to tgui/packages/tgui/interfaces/AreaScrubberControl.tsx index e9aa25cc8a..22e404dc84 100644 --- a/tgui/packages/tgui/interfaces/AreaScrubberControl.jsx +++ b/tgui/packages/tgui/interfaces/AreaScrubberControl.tsx @@ -1,3 +1,4 @@ +import { BooleanLike } from 'common/react'; import { toTitleCase } from 'common/string'; import { useState } from 'react'; @@ -5,10 +6,24 @@ import { useBackend } from '../backend'; import { Box, Button, Flex, LabeledList, Section } from '../components'; import { Window } from '../layouts'; -export const AreaScrubberControl = (props) => { - const { act, data } = useBackend(); +type scrubber = { + id: string; + name: string; + on: BooleanLike; + pressure: number; + flow_rate: number; + load: number; + area: string; +}; - const [showArea, setShowArea] = useState(false); +type Data = { + scrubbers: scrubber[]; +}; + +export const AreaScrubberControl = (props) => { + const { act, data } = useBackend(); + + const [showArea, setShowArea] = useState(false); const { scrubbers } = data; @@ -83,7 +98,7 @@ export const AreaScrubberControl = (props) => { ); }; -const BigScrubber = (props) => { +const BigScrubber = (props: { scrubber: scrubber; showArea: boolean }) => { const { act } = useBackend(); const { scrubber, showArea } = props; diff --git a/tgui/packages/tgui/interfaces/AssemblyProx.jsx b/tgui/packages/tgui/interfaces/AssemblyProx.tsx similarity index 81% rename from tgui/packages/tgui/interfaces/AssemblyProx.jsx rename to tgui/packages/tgui/interfaces/AssemblyProx.tsx index 7e1761bef7..a08e6f768a 100644 --- a/tgui/packages/tgui/interfaces/AssemblyProx.jsx +++ b/tgui/packages/tgui/interfaces/AssemblyProx.tsx @@ -1,12 +1,21 @@ import { round } from 'common/math'; +import { BooleanLike } from 'common/react'; import { useBackend } from '../backend'; import { Button, LabeledList, NumberInput, Section } from '../components'; import { formatTime } from '../format'; import { Window } from '../layouts'; +type Data = { + timing: number; + time: number; + range: number; + maxRange: number; + scanning: BooleanLike; +}; + export const AssemblyProx = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { timing, time, range, maxRange, scanning } = data; return ( @@ -31,8 +40,8 @@ export const AssemblyProx = (props) => { value={time} minValue={0} maxValue={600} - format={(val) => formatTime(round(val * 10))} - onDrag={(e, val) => act('set_time', { time: val })} + format={(val: number) => formatTime(round(val * 10, 0))} + onDrag={(e, val: string) => act('set_time', { time: val })} /> @@ -44,7 +53,7 @@ export const AssemblyProx = (props) => { minValue={1} value={range} maxValue={maxRange} - onDrag={(e, val) => act('range', { range: val })} + onDrag={(e, val: string) => act('range', { range: val })} /> diff --git a/tgui/packages/tgui/interfaces/AssemblyTimer.jsx b/tgui/packages/tgui/interfaces/AssemblyTimer.tsx similarity index 81% rename from tgui/packages/tgui/interfaces/AssemblyTimer.jsx rename to tgui/packages/tgui/interfaces/AssemblyTimer.tsx index 02d6f8b0e6..9fd6c8fb94 100644 --- a/tgui/packages/tgui/interfaces/AssemblyTimer.jsx +++ b/tgui/packages/tgui/interfaces/AssemblyTimer.tsx @@ -5,8 +5,10 @@ import { Button, LabeledList, NumberInput, Section } from '../components'; import { formatTime } from '../format'; import { Window } from '../layouts'; +type Data = { timing: number; time: number }; + export const AssemblyTimer = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { timing, time } = data; return ( @@ -31,8 +33,8 @@ export const AssemblyTimer = (props) => { value={time} minValue={0} maxValue={600} - format={(val) => formatTime(round(val * 10))} - onDrag={(e, val) => act('set_time', { time: val })} + format={(val: number) => formatTime(round(val * 10, 0))} + onDrag={(e, val: string) => act('set_time', { time: val })} /> diff --git a/tgui/packages/tgui/interfaces/AtmosAlertConsole.jsx b/tgui/packages/tgui/interfaces/AtmosAlertConsole.tsx similarity index 74% rename from tgui/packages/tgui/interfaces/AtmosAlertConsole.jsx rename to tgui/packages/tgui/interfaces/AtmosAlertConsole.tsx index c722c954c0..b2658aa66b 100644 --- a/tgui/packages/tgui/interfaces/AtmosAlertConsole.jsx +++ b/tgui/packages/tgui/interfaces/AtmosAlertConsole.tsx @@ -2,19 +2,24 @@ import { useBackend } from '../backend'; import { Button, Section } from '../components'; import { Window } from '../layouts'; +type alarm = { name: string; ref: string }; + +type Data = { priority_alarms: alarm[]; minor_alarms: alarm[] }; + export const AtmosAlertConsole = (props) => { - const { act, data } = useBackend(); - const priorityAlerts = data.priority_alarms || []; - const minorAlerts = data.minor_alarms || []; + const { act, data } = useBackend(); + + const { priority_alarms = [], minor_alarms = [] } = data; + return (
    - {priorityAlerts.length === 0 && ( + {priority_alarms.length === 0 && (
  • No Priority Alerts
  • )} - {priorityAlerts.map((alert) => ( + {priority_alarms.map((alert) => (
  • ))} - {minorAlerts.length === 0 && ( + {minor_alarms.length === 0 && (
  • No Minor Alerts
  • )} - {minorAlerts.map((alert) => ( + {minor_alarms.map((alert) => (
  • val + ' L/s'} /> + onDrag={(e, value: number) => act('rate', { rate: value, }) @@ -49,7 +61,7 @@ export const AtmosFilter = (props) => { - {filterTypes.map((filter) => ( + {filter_types.map((filter) => ( + onChange={(e, value: number) => act('pressure', { pressure: value, }) @@ -36,7 +59,7 @@ export const AtmosMixer = (props) => { { + const { act } = useBackend(); + const { bodyrecords } = props; + return ( +
    act('menu', { menu: 'Main' })}> + Back + + } + > + {bodyrecords + ? bodyrecords.map((record) => ( + + )) + : ''} +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerMain.tsx b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerMain.tsx new file mode 100644 index 0000000000..5da0f60516 --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerMain.tsx @@ -0,0 +1,16 @@ +import { useBackend } from '../../backend'; +import { Button, Section } from '../../components'; + +export const BodyDesignerMain = (props) => { + const { act } = useBackend(); + return ( +
    + + +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerOOCNotes.tsx b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerOOCNotes.tsx new file mode 100644 index 0000000000..f57b2f868c --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerOOCNotes.tsx @@ -0,0 +1,29 @@ +import { useBackend } from '../../backend'; +import { Button, Section } from '../../components'; +import { activeBodyRecord } from './types'; + +export const BodyDesignerOOCNotes = (props: { + activeBodyRecord: activeBodyRecord; +}) => { + const { act } = useBackend(); + const { activeBodyRecord } = props; + return ( +
    act('menu', { menu: 'Specific Record' })} + > + Back + + } + style={{ wordBreak: 'break-all' }} + > + {(activeBodyRecord && activeBodyRecord.booc) || + 'ERROR: Body record not found!'} +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/BodyDesigner.jsx b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerSpecificRecord.tsx similarity index 66% rename from tgui/packages/tgui/interfaces/BodyDesigner.jsx rename to tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerSpecificRecord.tsx index 3a9fcd4483..b6fd5e67f4 100644 --- a/tgui/packages/tgui/interfaces/BodyDesigner.jsx +++ b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerSpecificRecord.tsx @@ -1,6 +1,6 @@ import { capitalize } from 'common/string'; -import { useBackend } from '../backend'; +import { useBackend } from '../../backend'; import { Box, Button, @@ -9,115 +9,15 @@ import { Flex, LabeledList, Section, -} from '../components'; -import { Window } from '../layouts'; +} from '../../components'; +import { activeBodyRecord } from './types'; -export const BodyDesigner = (props) => { - const { act, data } = useBackend(); - - const { menu, disk, diskStored, activeBodyRecord } = data; - - let body = MenuToTemplate[menu]; - - return ( - - - {disk ? ( - - - - - - ) : null} - {body} - - - ); -}; - -const BodyDesignerMain = (props) => { - const { act, data } = useBackend(); - return ( -
    - - -
    - ); -}; - -const BodyDesignerBodyRecords = (props) => { - const { act, data } = useBackend(); - const { bodyrecords } = data; - return ( -
    act('menu', { menu: 'Main' })}> - Back - - } - > - {bodyrecords - ? bodyrecords.map((record) => ( - - )) - : ''} -
    - ); -}; - -const BodyDesignerStockRecords = (props) => { - const { act, data } = useBackend(); - const { stock_bodyrecords } = data; - return ( -
    act('menu', { menu: 'Main' })}> - Back - - } - > - {stock_bodyrecords.map((record) => ( - - ))} -
    - ); -}; - -const BodyDesignerSpecificRecord = (props) => { - const { act, data } = useBackend(); - const { activeBodyRecord, mapRef } = data; +export const BodyDesignerSpecificRecord = (props: { + activeBodyRecord: activeBodyRecord; + mapRef: string; +}) => { + const { act } = useBackend(); + const { activeBodyRecord, mapRef } = props; return activeBodyRecord ? ( @@ -325,35 +225,3 @@ const BodyDesignerSpecificRecord = (props) => { ERROR: Record Not Found! ); }; - -const BodyDesignerOOCNotes = (props) => { - const { act, data } = useBackend(); - const { activeBodyRecord } = data; - return ( -
    act('menu', { menu: 'Specific Record' })} - > - Back - - } - style={{ 'word-break': 'break-all' }} - > - {(activeBodyRecord && activeBodyRecord.booc) || - 'ERROR: Body record not found!'} -
    - ); -}; - -const MenuToTemplate = { - Main: , - 'Body Records': , - 'Stock Records': , - 'Specific Record': , - 'OOC Notes': , -}; diff --git a/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerStockRecords.tsx b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerStockRecords.tsx new file mode 100644 index 0000000000..71195d4f6d --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyDesigner/BodyDesignerStockRecords.tsx @@ -0,0 +1,29 @@ +import { useBackend } from '../../backend'; +import { Button, Section } from '../../components'; + +export const BodyDesignerStockRecords = (props: { + stock_bodyrecords: string[]; +}) => { + const { act } = useBackend(); + const { stock_bodyrecords } = props; + return ( +
    act('menu', { menu: 'Main' })}> + Back + + } + > + {stock_bodyrecords.map((record) => ( + + ))} +
    + ); +}; diff --git a/tgui/packages/tgui/interfaces/BodyDesigner/index.tsx b/tgui/packages/tgui/interfaces/BodyDesigner/index.tsx new file mode 100644 index 0000000000..be2178696f --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyDesigner/index.tsx @@ -0,0 +1,71 @@ +import { useBackend } from '../../backend'; +import { Box, Button } from '../../components'; +import { Window } from '../../layouts'; +import { BodyDesignerBodyRecords } from './BodyDesignerBodyRecords'; +import { BodyDesignerMain } from './BodyDesignerMain'; +import { BodyDesignerOOCNotes } from './BodyDesignerOOCNotes'; +import { BodyDesignerSpecificRecord } from './BodyDesignerSpecificRecord'; +import { BodyDesignerStockRecords } from './BodyDesignerStockRecords'; +import { Data } from './types'; + +export const BodyDesigner = (props) => { + const { act, data } = useBackend(); + + const { + menu, + disk, + diskStored, + activeBodyRecord, + stock_bodyrecords, + bodyrecords, + mapRef, + } = data; + + const MenuToTemplate = { + Main: , + 'Body Records': , + 'Stock Records': ( + + ), + 'Specific Record': ( + + ), + 'OOC Notes': , + }; + + let body = MenuToTemplate[menu]; + + return ( + + + {disk ? ( + + + + + + ) : ( + '' + )} + {body} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/BodyDesigner/types.ts b/tgui/packages/tgui/interfaces/BodyDesigner/types.ts new file mode 100644 index 0000000000..3ec032ee1e --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyDesigner/types.ts @@ -0,0 +1,63 @@ +import { BooleanLike } from 'common/react'; + +export type Data = { + mapRef: string; + bodyrecords: bodyrecord[]; + stock_bodyrecords: string[]; + activeBodyRecord: activeBodyRecord; + menu: string; + temp: { + styleHref: string; + style: string; + color: string | undefined; + colorHref: string | undefined; + color2?: string | undefined; + colorHref2?: string | undefined; + }; + disk: BooleanLike; + diskStored: BooleanLike; +}; + +export type bodyrecord = { name: string; recref: string }; + +export type activeBodyRecord = { + real_name: string; + speciesname: string; + gender: string; + synthetic: string; + locked: string; + scale: string; + booc: string; + styles: { + Ears: colourableStyle; + Tail: colourableStyle; + Wing: colourableStyle; + Hair: simpleStyle; + Facial: simpleStyle; + Eyes: colourStyle; + 'Body Color': colourStyle; + Bodytype: { styleHref: string; style: string }; + }; + markings: { name: Record }; // Record entries match BP regions +}; + +type colourableStyle = { + styleHref: string; + style: string; + color: string | undefined; + colorHref: string | undefined; + color2: string | undefined; + colorHref2: string | undefined; +}; + +type simpleStyle = { + styleHref: string; + style: string; + colorHref: string; + color: string; +}; + +type colourStyle = { + colorHref: string; + color: string; +}; diff --git a/tgui/packages/tgui/interfaces/BodyScanner.jsx b/tgui/packages/tgui/interfaces/BodyScanner.jsx deleted file mode 100644 index 25e34a31ad..0000000000 --- a/tgui/packages/tgui/interfaces/BodyScanner.jsx +++ /dev/null @@ -1,512 +0,0 @@ -import { toFixed } from 'common/math'; - -import { useBackend } from '../backend'; -import { - AnimatedNumber, - Box, - Button, - Flex, - Icon, - LabeledList, - ProgressBar, - Section, - Table, - Tooltip, -} from '../components'; -import { Window } from '../layouts'; - -const stats = [ - ['good', 'Alive'], - ['average', 'Unconscious'], - ['bad', 'DEAD'], -]; - -const abnormalities = [ - [ - 'hasBorer', - 'bad', - (occupant) => - 'Large growth detected in frontal lobe,' + - ' possibly cancerous. Surgical removal is recommended.', - ], - ['hasVirus', 'bad', (occupant) => 'Viral pathogen detected in blood stream.'], - ['blind', 'average', (occupant) => 'Cataracts detected.'], - [ - 'colourblind', - 'average', - (occupant) => 'Photoreceptor abnormalities detected.', - ], - ['nearsighted', 'average', (occupant) => 'Retinal misalignment detected.'], - /* VOREStation Add */ - [ - 'humanPrey', - 'average', - (occupant) => { - return 'Foreign Humanoid(s) detected: ' + occupant.humanPrey; - }, - ], - [ - 'livingPrey', - 'average', - (occupant) => { - return 'Foreign Creature(s) detected: ' + occupant.livingPrey; - }, - ], - [ - 'objectPrey', - 'average', - (occupant) => { - return 'Foreign Object(s) detected: ' + occupant.objectPrey; - }, - ], - /* VOREStation Add End */ -]; - -const damages = [ - ['Respiratory', 'oxyLoss'], - ['Brain', 'brainLoss'], - ['Toxin', 'toxLoss'], - ['Radiation', 'radLoss'], - ['Brute', 'bruteLoss'], - ['Genetic', 'cloneLoss'], - ['Burn', 'fireLoss'], - ['Paralysis', 'paralysis'], -]; - -const damageRange = { - average: [0.25, 0.5], - bad: [0.5, Infinity], -}; - -const mapTwoByTwo = (a, c) => { - let result = []; - for (let i = 0; i < a.length; i += 2) { - result.push(c(a[i], a[i + 1], i)); - } - return result; -}; - -const reduceOrganStatus = (A) => { - return A.length > 0 - ? A.reduce((a, s) => - a === null ? ( - s - ) : ( - <> - {a} - {!!s && {s}} - - ), - ) - : null; -}; - -const germStatus = (i) => { - if (i > 100) { - if (i < 300) { - return 'mild infection'; - } - if (i < 400) { - return 'mild infection+'; - } - if (i < 500) { - return 'mild infection++'; - } - if (i < 700) { - return 'acute infection'; - } - if (i < 800) { - return 'acute infection+'; - } - if (i < 900) { - return 'acute infection++'; - } - if (i >= 900) { - return 'septic'; - } - } - - return ''; -}; - -export const BodyScanner = (props) => { - const { data } = useBackend(); - const { occupied, occupant = {} } = data; - const body = occupied ? ( - - ) : ( - - ); - return ( - - - {body} - - - ); -}; - -const BodyScannerMain = (props) => { - const { occupant } = props; - return ( - - - - - - - - - ); -}; - -const BodyScannerMainOccupant = (props) => { - const { act, data } = useBackend(); - const { occupant } = data; - return ( -
    - - - - } - > - - {occupant.name} - - - - - {stats[occupant.stat][1]} - - - toFixed(value)} - /> - °C,  - toFixed(value)} - /> - °F - - - toFixed(value)} - /> - units ( - toFixed(value)} - /> - %) - - {/* VOREStation Add */} - - {toFixed(data.occupant.weight) + - 'lbs, ' + - toFixed(data.occupant.weight / 2.20463) + - 'kgs'} - - {/* VOREStation Add End */} - -
    - ); -}; - -const BodyScannerMainReagents = (props) => { - const { occupant } = props; - - return ( - <> -
    - {occupant.reagents ? ( -
- - Reagent - Amount - - {occupant.reagents.map((reagent) => ( - - {reagent.name} - - {reagent.amount} Units{' '} - {reagent.overdose ? OVERDOSING : null} - - - ))} -
- ) : ( - No Blood Reagents Detected - )} -
-
- {occupant.ingested ? ( - - - Reagent - Amount - - {occupant.ingested.map((reagent) => ( - - {reagent.name} - - {reagent.amount} Units{' '} - {reagent.overdose ? OVERDOSING : null} - - - ))} -
- ) : ( - No Stomach Reagents Detected - )} -
- - ); -}; - -const BodyScannerMainAbnormalities = (props) => { - const { occupant } = props; - - let hasAbnormalities = - occupant.hasBorer || - occupant.blind || - occupant.colourblind || - occupant.nearsighted || - occupant.hasVirus; - - /* VOREStation Add */ - hasAbnormalities = - hasAbnormalities || - occupant.humanPrey || - occupant.livingPrey || - occupant.objectPrey; - /* VOREStation Add End */ - - if (!hasAbnormalities) { - return ( -
- No abnormalities found. -
- ); - } - - return ( -
- {abnormalities.map((a, i) => { - if (occupant[a[0]]) { - return ( - - {a[2](occupant)} - - ); - } - })} -
- ); -}; - -const BodyScannerMainDamage = (props) => { - const { occupant } = props; - return ( -
- - {mapTwoByTwo(damages, (d1, d2, i) => ( - <> - - {d1[0]}: - {!!d2 && d2[0] + ':'} - - - - - - - {!!d2 && } - - - - ))} -
-
- ); -}; - -const BodyScannerMainDamageBar = (props) => { - return ( - - {toFixed(props.value)} - - ); -}; - -const BodyScannerMainOrgansExternal = (props) => { - if (props.organs.length === 0) { - return ( -
- N/A -
- ); - } - - return ( -
- - - Name - Damage - Injuries - - {props.organs.map((o, i) => ( - - {o.name} - - 0 && '0.5rem'} - value={o.totalLoss / 100} - ranges={damageRange} - > - - {!!o.bruteLoss && ( - - - {toFixed(o.bruteLoss)}  - - - )} - {!!o.fireLoss && ( - - - {toFixed(o.fireLoss)} - - - )} - - {toFixed(o.totalLoss)} - - - - - {reduceOrganStatus([ - o.internalBleeding && 'Internal bleeding', - !!o.status.bleeding && 'External bleeding', - o.lungRuptured && 'Ruptured lung', - o.destroyed && 'Destroyed', - !!o.status.broken && o.status.broken, - germStatus(o.germ_level), - !!o.open && 'Open incision', - ])} - - - {reduceOrganStatus([ - !!o.status.splinted && 'Splinted', - !!o.status.robotic && 'Robotic', - !!o.status.dead && DEAD, - ])} - {reduceOrganStatus( - o.implants.map((s) => (s.known ? s.name : 'Unknown object')), - )} - - - - ))} -
-
- ); -}; - -const BodyScannerMainOrgansInternal = (props) => { - if (props.organs.length === 0) { - return ( -
- N/A -
- ); - } - - return ( -
- - - Name - Damage - Injuries - - {props.organs.map((o, i) => ( - - {o.name} - - 0 && '0.5rem'} - ranges={damageRange} - > - {toFixed(o.damage)} - - - - - {reduceOrganStatus([ - germStatus(o.germ_level), - !!o.inflamed && 'Appendicitis detected.', - ])} - - - {reduceOrganStatus([ - o.robotic === 1 && 'Robotic', - o.robotic === 2 && 'Assisted', - !!o.dead && DEAD, - ])} - - - - ))} -
-
- ); -}; - -const BodyScannerEmpty = () => { - return ( -
- - - -
- No occupant detected. -
-
-
- ); -}; diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerEmpty.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerEmpty.tsx new file mode 100644 index 0000000000..e6f0f4bae9 --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerEmpty.tsx @@ -0,0 +1,15 @@ +import { Flex, Icon, Section } from '../../components'; + +export const BodyScannerEmpty = () => { + return ( +
+ + + +
+ No occupant detected. +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMain.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMain.tsx new file mode 100644 index 0000000000..28237b0b04 --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMain.tsx @@ -0,0 +1,22 @@ +import { Box } from '../../components'; +import { BodyScannerMainAbnormalities } from './BodyScannerMainAbnormalities'; +import { BodyScannerMainDamage } from './BodyScannerMainDamage'; +import { BodyScannerMainOccupant } from './BodyScannerMainOccupant'; +import { BodyScannerMainOrgansExternal } from './BodyScannerMainOrgansExternal'; +import { BodyScannerMainOrgansInternal } from './BodyScannerMainOrgansInternal'; +import { BodyScannerMainReagents } from './BodyScannerMainReagents'; +import { occupant } from './types'; + +export const BodyScannerMain = (props: { occupant: occupant }) => { + const { occupant } = props; + return ( + + + + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainAbnormalities.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainAbnormalities.tsx new file mode 100644 index 0000000000..e68604d909 --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainAbnormalities.tsx @@ -0,0 +1,42 @@ +import { Box, Section } from '../../components'; +import { abnormalities } from './constants'; +import { occupant } from './types'; + +export const BodyScannerMainAbnormalities = (props: { occupant: occupant }) => { + const { occupant } = props; + + let hasAbnormalities = + occupant.hasBorer || + occupant.blind || + occupant.colourblind || + occupant.nearsighted || + occupant.hasVirus; + + hasAbnormalities = + hasAbnormalities || + occupant.humanPrey || + occupant.livingPrey || + occupant.objectPrey; + + if (!hasAbnormalities) { + return ( +
+ No abnormalities found. +
+ ); + } + + return ( +
+ {abnormalities.map((a, i) => { + if (occupant[a[0] as string]) { + return ( + + {(a[2] as (occupant: occupant) => string)(occupant)} + + ); + } + })} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainDamage.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainDamage.tsx new file mode 100644 index 0000000000..6b84636138 --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainDamage.tsx @@ -0,0 +1,54 @@ +import { toFixed } from 'common/math'; + +import { ProgressBar, Section, Table } from '../../components'; +import { damageRange, damages } from './constants'; +import { mapTwoByTwo } from './functions'; +import { occupant } from './types'; + +export const BodyScannerMainDamage = (props: { occupant: occupant }) => { + const { occupant } = props; + return ( +
+ + {mapTwoByTwo(damages, (d1: string[], d2: string[], i: number) => ( + <> + + {d1[0]}: + {!!d2 && d2[0] + ':'} + + + + + + + {!!d2 && } + + + + ))} +
+
+ ); +}; + +const BodyScannerMainDamageBar = (props: { + value: number; + marginBottom?: boolean; +}) => { + const { value, marginBottom } = props; + return ( + + {toFixed(value)} + + ); +}; diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOccupant.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOccupant.tsx new file mode 100644 index 0000000000..95f54a5cd9 --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOccupant.tsx @@ -0,0 +1,81 @@ +import { toFixed } from 'common/math'; + +import { useBackend } from '../../backend'; +import { + AnimatedNumber, + Button, + LabeledList, + ProgressBar, + Section, +} from '../../components'; +import { stats } from './constants'; +import { occupant } from './types'; + +export const BodyScannerMainOccupant = (props: { occupant: occupant }) => { + const { act } = useBackend(); + const { occupant } = props; + return ( +
+ + + + } + > + + {occupant.name} + + + + + {stats[occupant.stat][1]} + + + toFixed(value)} + /> + °C,  + toFixed(value)} + /> + °F + + + toFixed(value)} + /> + units ( + toFixed(value)} + /> + %) + + + {toFixed(occupant.weight) + + 'lbs, ' + + toFixed(occupant.weight / 2.20463) + + 'kgs'} + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOrgansExternal.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOrgansExternal.tsx new file mode 100644 index 0000000000..cd32c84a4a --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOrgansExternal.tsx @@ -0,0 +1,99 @@ +import { toFixed } from 'common/math'; + +import { + Box, + Icon, + ProgressBar, + Section, + Table, + Tooltip, +} from '../../components'; +import { damageRange } from './constants'; +import { germStatus, reduceOrganStatus } from './functions'; +import { externalOrgan } from './types'; + +export const BodyScannerMainOrgansExternal = (props: { + organs: externalOrgan[]; +}) => { + const { organs } = props; + + if (organs.length === 0) { + return ( +
+ N/A +
+ ); + } + + return ( +
+ + + Name + Damage + Injuries + + {organs.map((o, i) => ( + + {o.name} + + 0 && '0.5rem'} + value={o.totalLoss / 100} + ranges={damageRange} + > + + {!!o.bruteLoss && ( + + + {toFixed(o.bruteLoss)}  + + + )} + {!!o.fireLoss && ( + + + {toFixed(o.fireLoss)} + + + )} + + {toFixed(o.totalLoss)} + + + + + {reduceOrganStatus([ + o.internalBleeding && 'Internal bleeding', + !!o.status.bleeding && 'External bleeding', + o.lungRuptured && 'Ruptured lung', + o.status.destroyed && 'Destroyed', + !!o.status.broken && o.status.broken, + germStatus(o.germ_level), + !!o.open && 'Open incision', + ])} + + + {reduceOrganStatus([ + !!o.status.splinted && 'Splinted', + !!o.status.robotic && 'Robotic', + !!o.status.dead && DEAD, + ])} + {reduceOrganStatus( + o.implants.map((s) => (s.known ? s.name : 'Unknown object')), + )} + + + + ))} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOrgansInternal.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOrgansInternal.tsx new file mode 100644 index 0000000000..af44217f2d --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainOrgansInternal.tsx @@ -0,0 +1,63 @@ +import { toFixed } from 'common/math'; + +import { Box, ProgressBar, Section, Table } from '../../components'; +import { damageRange } from './constants'; +import { germStatus, reduceOrganStatus } from './functions'; +import { internalOrgan } from './types'; + +export const BodyScannerMainOrgansInternal = (props: { + organs: internalOrgan[]; +}) => { + const { organs } = props; + + if (organs.length === 0) { + return ( +
+ N/A +
+ ); + } + + return ( +
+ + + Name + Damage + Injuries + + {organs.map((o, i) => ( + + {o.name} + + 0 && '0.5rem'} + ranges={damageRange} + > + {toFixed(o.damage)} + + + + + {reduceOrganStatus([ + germStatus(o.germ_level), + !!o.inflamed && 'Appendicitis detected.', + ])} + + + {reduceOrganStatus([ + o.robotic === 1 && 'Robotic', + o.robotic === 2 && 'Assisted', + !!o.dead && DEAD, + ])} + + + + ))} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainReagents.tsx b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainReagents.tsx new file mode 100644 index 0000000000..922cb4059a --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyScanner/BodyScannerMainReagents.tsx @@ -0,0 +1,53 @@ +import { Box, Section, Table } from '../../components'; +import { occupant } from './types'; + +export const BodyScannerMainReagents = (props: { occupant: occupant }) => { + const { occupant } = props; + + return ( + <> +
+ {occupant.reagents ? ( + + + Reagent + Amount + + {occupant.reagents.map((reagent) => ( + + {reagent.name} + + {reagent.amount} Units{' '} + {reagent.overdose ? OVERDOSING : null} + + + ))} +
+ ) : ( + No Blood Reagents Detected + )} +
+
+ {occupant.ingested ? ( + + + Reagent + Amount + + {occupant.ingested.map((reagent) => ( + + {reagent.name} + + {reagent.amount} Units{' '} + {reagent.overdose ? OVERDOSING : null} + + + ))} +
+ ) : ( + No Stomach Reagents Detected + )} +
+ + ); +}; diff --git a/tgui/packages/tgui/interfaces/BodyScanner/constants.ts b/tgui/packages/tgui/interfaces/BodyScanner/constants.ts new file mode 100644 index 0000000000..14dfe60770 --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyScanner/constants.ts @@ -0,0 +1,62 @@ +import { occupant } from './types'; + +export const stats: string[][] = [ + ['good', 'Alive'], + ['average', 'Unconscious'], + ['bad', 'DEAD'], +]; + +export const abnormalities: (string | ((occupant: occupant) => string))[][] = [ + [ + 'hasBorer', + 'bad', + (occupant) => + 'Large growth detected in frontal lobe,' + + ' possibly cancerous. Surgical removal is recommended.', + ], + ['hasVirus', 'bad', (occupant) => 'Viral pathogen detected in blood stream.'], + ['blind', 'average', (occupant) => 'Cataracts detected.'], + [ + 'colourblind', + 'average', + (occupant) => 'Photoreceptor abnormalities detected.', + ], + ['nearsighted', 'average', (occupant) => 'Retinal misalignment detected.'], + [ + 'humanPrey', + 'average', + (occupant) => { + return 'Foreign Humanoid(s) detected: ' + occupant.humanPrey; + }, + ], + [ + 'livingPrey', + 'average', + (occupant) => { + return 'Foreign Creature(s) detected: ' + occupant.livingPrey; + }, + ], + [ + 'objectPrey', + 'average', + (occupant) => { + return 'Foreign Object(s) detected: ' + occupant.objectPrey; + }, + ], +]; + +export const damages: string[][] = [ + ['Respiratory', 'oxyLoss'], + ['Brain', 'brainLoss'], + ['Toxin', 'toxLoss'], + ['Radiation', 'radLoss'], + ['Brute', 'bruteLoss'], + ['Genetic', 'cloneLoss'], + ['Burn', 'fireLoss'], + ['Paralysis', 'paralysis'], +]; + +export const damageRange: Record = { + average: [0.25, 0.5], + bad: [0.5, Infinity], +}; diff --git a/tgui/packages/tgui/interfaces/BodyScanner/functions.tsx b/tgui/packages/tgui/interfaces/BodyScanner/functions.tsx new file mode 100644 index 0000000000..3c43d892f0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyScanner/functions.tsx @@ -0,0 +1,58 @@ +import { BooleanLike } from 'common/react'; + +import { Box } from '../../components'; + +/* + */ +export function mapTwoByTwo(a: any[][], c: any) { + let result: any[] = []; + for (let i = 0; i < a.length; i += 2) { + result.push(c(a[i], a[i + 1], i)); + } + return result; +} + +export function reduceOrganStatus( + A: (string | BooleanLike | React.ReactElement)[], +) { + return A.length > 0 + ? A.reduce((a, s) => + a === null ? ( + s + ) : ( + <> + {a} + {!!s && {s}} + + ), + ) + : null; +} + +export function germStatus(i: number): string { + if (i > 100) { + if (i < 300) { + return 'mild infection'; + } + if (i < 400) { + return 'mild infection+'; + } + if (i < 500) { + return 'mild infection++'; + } + if (i < 700) { + return 'acute infection'; + } + if (i < 800) { + return 'acute infection+'; + } + if (i < 900) { + return 'acute infection++'; + } + if (i >= 900) { + return 'septic'; + } + } + + return ''; +} diff --git a/tgui/packages/tgui/interfaces/BodyScanner/index.tsx b/tgui/packages/tgui/interfaces/BodyScanner/index.tsx new file mode 100644 index 0000000000..d44bc0d78b --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyScanner/index.tsx @@ -0,0 +1,23 @@ +import { useBackend } from '../../backend'; +import { Window } from '../../layouts'; +import { BodyScannerEmpty } from './BodyScannerEmpty'; +import { BodyScannerMain } from './BodyScannerMain'; +import { Data, occupant } from './types'; + +export const BodyScanner = (props) => { + const { data } = useBackend(); + const { occupied, occupant = {} as occupant } = data; + const body = occupied ? ( + + ) : ( + + ); + return ( + + + {body} + + + ); +}; +6; diff --git a/tgui/packages/tgui/interfaces/BodyScanner/types.ts b/tgui/packages/tgui/interfaces/BodyScanner/types.ts new file mode 100644 index 0000000000..45aacd61ce --- /dev/null +++ b/tgui/packages/tgui/interfaces/BodyScanner/types.ts @@ -0,0 +1,77 @@ +import { BooleanLike } from 'common/react'; + +export type Data = { + occupied: BooleanLike; + occupant: occupant; +}; + +export type occupant = { + name: string; + stat: number; + health: number; + maxHealth: number; + hasVirus: number; + bruteLoss: number; + oxyLoss: number; + toxLoss: number; + fireLoss: number; + radLoss: number; + cloneLoss: number; + brainLoss: number; + paralysis: number; + paralysisSeconds: number; + bodyTempC: number; + bodyTempF: number; + hasBorer: BooleanLike; + colourblind: BooleanLike; + blood: { volume: number; percent: number }; + reagents: reagent[]; + ingested: reagent[]; + extOrgan: externalOrgan[]; + intOrgan: internalOrgan[]; + blind: BooleanLike; + nearsighted: BooleanLike; + livingPrey: number; + humanPrey: number; + objectPrey: number; + weight: number; +}; + +type reagent = { name: string; amount: number; overdose: BooleanLike }; + +export type internalOrgan = { + name: string; + desc: string | null; + germ_level: number; + damage: number; + maxHealth: number; + bruised: number; + broken: number; + robotic: BooleanLike; + dead: BooleanLike; + inflamed: BooleanLike; +}; + +export type externalOrgan = { + name: string; + open: BooleanLike; + germ_level: number; + bruteLoss: number; + fireLoss: number; + totalLoss: number; + maxHealth: number; + bruised: number; + broken: number; + implants: { name: string; known: BooleanLike }[]; + implants_len: number; + status: { + destroyed: BooleanLike; + broken: string; + robotic: BooleanLike; + splinted: BooleanLike; + bleeding: BooleanLike; + dead: BooleanLike; + }; + lungRuptured: BooleanLike; + internalBleeding: BooleanLike; +}; diff --git a/tgui/packages/tgui/interfaces/BombTester.jsx b/tgui/packages/tgui/interfaces/BombTester.tsx similarity index 88% rename from tgui/packages/tgui/interfaces/BombTester.jsx rename to tgui/packages/tgui/interfaces/BombTester.tsx index c1e1a2afe9..1fe140210f 100644 --- a/tgui/packages/tgui/interfaces/BombTester.jsx +++ b/tgui/packages/tgui/interfaces/BombTester.tsx @@ -1,11 +1,23 @@ +import { BooleanLike } from 'common/react'; import { Component } from 'react'; import { useBackend } from '../backend'; import { Box, Button, Icon, LabeledList, Section, Slider } from '../components'; import { Window } from '../layouts'; +type Data = { + simulating: BooleanLike; + mode: number; + tank1: string; + tank1ref: string; + tank2: string; + tank2ref: string; + canister: string | null; + sim_canister_output: number; +}; + export const BombTester = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { simulating, @@ -96,7 +108,7 @@ export const BombTester = (props) => { minValue={0} value={sim_canister_output} maxValue={1013.25} - onDrag={(e, val) => + onDrag={(e, val: number) => act('set_can_pressure', { pressure: val }) } /> @@ -120,16 +132,26 @@ export const BombTester = (props) => { ); }; +type stateType = { + reverseX: boolean; + reverseY: boolean; + x: number; + y: number; +}; + class BombTesterSimulation extends Component { + process: NodeJS.Timeout; + state: stateType; + constructor(props) { super(props); - const BOUND_X = 340; - const BOUND_Y = 205; - const MOVEMENT_SPEED = 2; + const BOUND_X: number = 340; + const BOUND_Y: number = 205; + const MOVEMENT_SPEED: number = 2; - let startRight = Math.random() > 0.5; - let startBottom = Math.random() > 0.5; + let startRight: boolean = Math.random() > 0.5; + let startBottom: boolean = Math.random() > 0.5; this.state = { x: startRight ? BOUND_X : 0, @@ -139,7 +161,7 @@ class BombTesterSimulation extends Component { }; this.process = setInterval(() => { - this.setState((prevState) => { + this.setState((prevState: stateType) => { const state = { ...prevState }; if (state.reverseX) { if (state.x - MOVEMENT_SPEED < -5) { diff --git a/tgui/packages/tgui/interfaces/BotanyEditor.jsx b/tgui/packages/tgui/interfaces/BotanyEditor.tsx similarity index 88% rename from tgui/packages/tgui/interfaces/BotanyEditor.jsx rename to tgui/packages/tgui/interfaces/BotanyEditor.tsx index 5210e31dd7..b870981289 100644 --- a/tgui/packages/tgui/interfaces/BotanyEditor.jsx +++ b/tgui/packages/tgui/interfaces/BotanyEditor.tsx @@ -1,9 +1,20 @@ +import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; import { Box, Button, LabeledList, NoticeBox, Section } from '../components'; import { Window } from '../layouts'; +type Data = { + activity: BooleanLike; + degradation: number; + disk: BooleanLike; + loaded: string | number; + sourceName: string; + locus: string[]; +}; + export const BotanyEditor = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { activity, degradation, disk, sourceName, locus, loaded } = data; diff --git a/tgui/packages/tgui/interfaces/BotanyIsolator.jsx b/tgui/packages/tgui/interfaces/BotanyIsolator.tsx similarity index 91% rename from tgui/packages/tgui/interfaces/BotanyIsolator.jsx rename to tgui/packages/tgui/interfaces/BotanyIsolator.tsx index b46e434268..12c8e45e2c 100644 --- a/tgui/packages/tgui/interfaces/BotanyIsolator.jsx +++ b/tgui/packages/tgui/interfaces/BotanyIsolator.tsx @@ -1,9 +1,21 @@ +import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; import { Box, Button, LabeledList, NoticeBox, Section } from '../components'; import { Window } from '../layouts'; +type Data = { + geneMasks: { tag: string; mask: string }[]; + activity: BooleanLike; + degradation: number; + disk: BooleanLike; + loaded: string | number; + hasGenetics: BooleanLike; + sourceName: string; +}; + export const BotanyIsolator = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { geneMasks, diff --git a/tgui/packages/tgui/interfaces/BrigTimer.jsx b/tgui/packages/tgui/interfaces/BrigTimer.tsx similarity index 60% rename from tgui/packages/tgui/interfaces/BrigTimer.jsx rename to tgui/packages/tgui/interfaces/BrigTimer.tsx index 44e693a609..a0ab78e35d 100644 --- a/tgui/packages/tgui/interfaces/BrigTimer.jsx +++ b/tgui/packages/tgui/interfaces/BrigTimer.tsx @@ -1,12 +1,36 @@ import { round } from 'common/math'; +import { BooleanLike } from 'common/react'; import { useBackend } from '../backend'; import { Button, Flex, NumberInput, Section } from '../components'; import { formatTime } from '../format'; import { Window } from '../layouts'; +type Data = { + time_left: number; + max_time_left: number; + timing: BooleanLike; + flash_found: BooleanLike; + flash_charging: BooleanLike; + preset_short: number; + preset_medium: number; + preset_long: number; +}; + export const BrigTimer = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); + + const { + time_left, + max_time_left, + timing, + flash_found, + flash_charging, + preset_short, + preset_medium, + preset_long, + } = data; + return ( @@ -16,18 +40,18 @@ export const BrigTimer = (props) => { <> - {(data.flash_found && ( + {(flash_found && ( )) || null} @@ -37,11 +61,11 @@ export const BrigTimer = (props) => { formatTime(round(val * 10))} - onDrag={(e, val) => act('time', { time: val })} + maxValue={max_time_left / 10} + format={(val: number) => formatTime(round(val * 10, 0))} + onDrag={(e, val: number) => act('time', { time: val })} /> @@ -50,7 +74,7 @@ export const BrigTimer = (props) => { icon="hourglass-start" onClick={() => act('preset', { preset: 'short' })} > - {'Add ' + formatTime(data.preset_short)} + {'Add ' + formatTime(preset_short)} @@ -59,7 +83,7 @@ export const BrigTimer = (props) => { icon="hourglass-start" onClick={() => act('preset', { preset: 'medium' })} > - {'Add ' + formatTime(data.preset_medium)} + {'Add ' + formatTime(preset_medium)} @@ -68,7 +92,7 @@ export const BrigTimer = (props) => { icon="hourglass-start" onClick={() => act('preset', { preset: 'long' })} > - {'Add ' + formatTime(data.preset_long)} + {'Add ' + formatTime(preset_long)} diff --git a/tgui/packages/tgui/interfaces/CameraConsole.jsx b/tgui/packages/tgui/interfaces/CameraConsole.tsx similarity index 74% rename from tgui/packages/tgui/interfaces/CameraConsole.jsx rename to tgui/packages/tgui/interfaces/CameraConsole.tsx index 29ee68a38a..f558de6398 100644 --- a/tgui/packages/tgui/interfaces/CameraConsole.jsx +++ b/tgui/packages/tgui/interfaces/CameraConsole.tsx @@ -1,6 +1,6 @@ import { filter, sortBy } from 'common/collections'; import { flow } from 'common/fp'; -import { classes } from 'common/react'; +import { BooleanLike, classes } from 'common/react'; import { createSearch } from 'common/string'; import { useState } from 'react'; @@ -8,11 +8,25 @@ import { useBackend } from '../backend'; import { Button, ByondUi, Dropdown, Flex, Input, Section } from '../components'; import { Window } from '../layouts'; +type activeCamera = { name: string; status: BooleanLike } | null; + +type camera = { name: string; networks: string[] }; + +export type Data = { + activeCamera: activeCamera; + mapRef: string; + cameras: camera[]; + allNetworks: string[]; +}; + /** * Returns previous and next camera names relative to the currently * active camera. */ -export const prevNextCamera = (cameras, activeCamera) => { +export const prevNextCamera = ( + cameras: camera[], + activeCamera: activeCamera, +) => { if (!activeCamera) { return []; } @@ -22,32 +36,43 @@ export const prevNextCamera = (cameras, activeCamera) => { return [cameras[index - 1]?.name, cameras[index + 1]?.name]; }; +function notEmpty(value: TValue | null | undefined): value is TValue { + return value !== null && value !== undefined; +} + /** * Camera selector. * * Filters cameras, applies search terms and sorts the alphabetically. */ -export const selectCameras = (cameras, searchText = '', networkFilter = '') => { - const testSearch = createSearch(searchText, (camera) => camera.name); +export const selectCameras = ( + cameras: camera[], + searchText: string = '', + networkFilter: string = '', +) => { + const testSearch = createSearch(searchText, (camera: camera) => camera.name); return flow([ // Null camera filter - filter((camera) => camera?.name), + filter((camera: camera) => notEmpty(camera?.name)), // Optional search term searchText && filter(testSearch), // Optional network filter networkFilter && - filter((camera) => camera.networks.includes(networkFilter)), + filter((camera: camera) => camera.networks.includes(networkFilter)), // Slightly expensive, but way better than sorting in BYOND - sortBy((camera) => camera.name), + sortBy((camera: camera) => camera.name), ])(cameras); }; export const CameraConsole = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { mapRef, activeCamera } = data; - const cameras = selectCameras(data.cameras); - const [prevCameraName, nextCameraName] = prevNextCamera( - cameras, + + const { cameras } = data; + + const selected_cameras: camera[] = selectCameras(cameras); + const [prevCameraName, nextCameraName]: string[] = prevNextCamera( + selected_cameras, activeCamera, ); return ( @@ -101,12 +126,16 @@ export const CameraConsole = (props) => { }; export const CameraConsoleContent = (props) => { - const { act, data } = useBackend(); - const [searchText, setSearchText] = useState(''); - const [networkFilter, setNetworkFilter] = useState(''); - const { activeCamera, allNetworks } = data; + const { act, data } = useBackend(); + const [searchText, setSearchText] = useState(''); + const [networkFilter, setNetworkFilter] = useState(''); + const { activeCamera, allNetworks, cameras } = data; allNetworks.sort(); - const cameras = selectCameras(data.cameras, searchText, networkFilter); + const selected_cameras: camera[] = selectCameras( + cameras, + searchText, + networkFilter, + ); return ( @@ -115,7 +144,7 @@ export const CameraConsoleContent = (props) => { fluid mt={1} placeholder="Search for a camera" - onInput={(e, value) => setSearchText(value)} + onInput={(e, value: string) => setSearchText(value)} /> @@ -147,7 +176,7 @@ export const CameraConsoleContent = (props) => {
- {cameras.map((camera) => ( + {selected_cameras.map((camera) => ( // We're not using the component here because performance // would be absolutely abysmal (50+ ms for each re-render).
{ - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { connected, can_relabel, @@ -66,7 +79,7 @@ export const Canister = (props) => { minValue={minReleasePressure} maxValue={maxReleasePressure} stepPixelSize={1} - onDrag={(e, value) => + onDrag={(e, value: number) => act('pressure', { pressure: value, }) diff --git a/tgui/packages/tgui/interfaces/Canvas.jsx b/tgui/packages/tgui/interfaces/Canvas.tsx similarity index 69% rename from tgui/packages/tgui/interfaces/Canvas.jsx rename to tgui/packages/tgui/interfaces/Canvas.tsx index 4d5d7015ee..d54eb7e66c 100644 --- a/tgui/packages/tgui/interfaces/Canvas.jsx +++ b/tgui/packages/tgui/interfaces/Canvas.tsx @@ -1,4 +1,5 @@ -import { Component, createRef } from 'react'; +import { BooleanLike } from 'common/react'; +import { Component, createRef, RefObject } from 'react'; import { useBackend } from '../backend'; import { Box, Button } from '../components'; @@ -6,7 +7,17 @@ import { Window } from '../layouts'; const PX_PER_UNIT = 24; -class PaintCanvas extends Component { +type PaintCanvasProps = Partial<{ + onCanvasClick: (x: number, y: number) => void; + value: string[][]; + dotsize: number; + res: number; +}>; + +class PaintCanvas extends Component { + canvasRef: RefObject; + onCVClick: (x: number, y: number) => void; + constructor(props) { super(props); this.canvasRef = createRef(); @@ -21,16 +32,20 @@ class PaintCanvas extends Component { this.drawCanvas(this.props); } - drawCanvas(propSource) { - const ctx = this.canvasRef.current.getContext('2d'); + drawCanvas(propSource: PaintCanvasProps) { + const canvas = this.canvasRef.current!; + const ctx = canvas.getContext('2d')!; const grid = propSource.value; + if (!grid) { + return; + } const x_size = grid.length; if (!x_size) { return; } const y_size = grid[0].length; - const x_scale = Math.round(this.canvasRef.current.width / x_size); - const y_scale = Math.round(this.canvasRef.current.height / y_size); + const x_scale = Math.round(canvas.width / x_size); + const y_scale = Math.round(canvas.height / y_size); ctx.save(); ctx.scale(x_scale, y_scale); for (let x = 0; x < grid.length; x++) { @@ -44,14 +59,19 @@ class PaintCanvas extends Component { ctx.restore(); } - clickwrapper(event) { - const x_size = this.props.value.length; + clickwrapper(event: React.MouseEvent) { + const value = this.props.value; + if (!value) { + return; + } + const x_size = value.length; if (!x_size) { return; } const y_size = this.props.value[0].length; - const x_scale = this.canvasRef.current.width / x_size; - const y_scale = this.canvasRef.current.height / y_size; + const canvas = this.canvasRef.current!; + const x_scale = canvas.width / x_size; + const y_scale = canvas.height / y_size; const x = Math.floor(event.nativeEvent.offsetX / x_scale) + 1; const y = Math.floor(event.nativeEvent.offsetY / y_scale) + 1; this.onCVClick(x, y); @@ -80,8 +100,15 @@ const getImageSize = (value) => { return [width, height]; }; +type Data = { + grid: string[][]; + name: string; + finalized: BooleanLike; +}; + export const Canvas = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); + const dotsize = PX_PER_UNIT; const [width, height] = getImageSize(data.grid); return ( diff --git a/tgui/packages/tgui/interfaces/CasinoPrizeDispenser.jsx b/tgui/packages/tgui/interfaces/CasinoPrizeDispenser.tsx similarity index 81% rename from tgui/packages/tgui/interfaces/CasinoPrizeDispenser.jsx rename to tgui/packages/tgui/interfaces/CasinoPrizeDispenser.tsx index 8e03666a51..6c13f446db 100644 --- a/tgui/packages/tgui/interfaces/CasinoPrizeDispenser.jsx +++ b/tgui/packages/tgui/interfaces/CasinoPrizeDispenser.tsx @@ -13,26 +13,36 @@ import { } from '../components'; import { Window } from '../layouts'; +type Data = { + items: Record; +}; + +type sortable = { + name: string; + affordable: number; + price: number; + restriction: string; +}; + const sortTypes = { - Alphabetical: (a, b) => a.name > b.name, - 'By availability': (a, b) => -(a.affordable - b.affordable), - 'By price': (a, b) => a.price - b.price, + Alphabetical: (a: sortable, b: sortable) => a.name > b.name, + 'By price': (a: sortable, b: sortable) => a.price - b.price, }; export const CasinoPrizeDispenser = () => { - const [searchText, setSearchText] = useState(''); - const [sortOrder, setSortOrder] = useState('Alphabetical'); - const [descending, setDescending] = useState(false); + const [searchText, setSearchText] = useState(''); + const [sortOrder, setSortOrder] = useState('Alphabetical'); + const [descending, setDescending] = useState(false); - function handleSearchText(value) { + function handleSearchText(value: string) { setSearchText(value); } - function handleSortOrder(value) { + function handleSortOrder(value: string) { setSortOrder(value); } - function handleDescending(value) { + function handleDescending(value: boolean) { setDescending(value); } @@ -52,9 +62,6 @@ export const CasinoPrizeDispenser = () => { searchText={searchText} sortOrder={sortOrder} descending={descending} - onSearchText={handleSearchText} - onSortOrder={handleSortOrder} - onDescending={handleDescending} /> @@ -98,19 +105,18 @@ const CasinoPrizeDispenserSearch = (props) => { }; const CasinoPrizeDispenserItems = (props) => { - const { act, data } = useBackend(); - const { points, items } = data; + const { act, data } = useBackend(); + const { items } = data; // Search thingies const searcher = createSearch(props.searchText, (item) => { return item[0]; }); let has_contents = false; - let contents = Object.entries(items).map((kv, _i) => { + let contents = Object.entries(items).map((kv) => { let items_in_cat = Object.entries(kv[1]) .filter(searcher) .map((kv2) => { - kv2[1].affordable = points >= kv2[1].price; return kv2[1]; }) .sort(sortTypes[props.sortOrder]); @@ -143,15 +149,19 @@ const CasinoPrizeDispenserItems = (props) => { ); }; -const CasinoPrizeDispenserItemsCategory = (props) => { - const { act, data } = useBackend(); +const CasinoPrizeDispenserItemsCategory = (props: { + key: string; + title: string; + items: sortable[]; +}) => { + const { act } = useBackend(); const { title, items, ...rest } = props; return ( {items.map((item) => ( { +const getTagColor = (tag: string) => { switch (tag) { case 'Unset': return 'label'; @@ -23,19 +24,36 @@ const getTagColor = (tag) => { } }; +type Data = { + personalVisibility: BooleanLike; + personalTag: string; + personalErpTag: string; + directory: character[]; +}; + +type character = { + name: string; + species: string; + ooc_notes: string; + tag: string; + erptag: string; + character_ad: string; + flavor_text: string; +}; + export const CharacterDirectory = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { personalVisibility, personalTag, personalErpTag } = data; - const [overlay, setOverlay] = useState(null); - const [overwritePrefs, setOverwritePrefs] = useState(false); + const [overlay, setOverlay] = useState(null); + const [overwritePrefs, setOverwritePrefs] = useState(false); - function handleOverlay(value) { + function handleOverlay(value: character | null) { setOverlay(value); } return ( - + {(overlay && ( @@ -119,29 +137,29 @@ const ViewCharacter = (props) => { } > -
+
{props.overlay.species}
-
+
{props.overlay.tag}
-
+
{props.overlay.erptag}
-
- +
+ {props.overlay.character_ad || 'Unset.'}
-
- +
+ {props.overlay.ooc_notes || 'Unset.'}
-
- +
+ {props.overlay.flavor_text || 'Unset.'}
@@ -150,17 +168,17 @@ const ViewCharacter = (props) => { }; const CharacterDirectoryList = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { directory } = data; - const [sortId, setSortId] = useState('name'); - const [sortOrder, setSortOrder] = useState('name'); + const [sortId, setSortId] = useState('name'); + const [sortOrder, setSortOrder] = useState('name'); - function handleSortId(value) { + function handleSortId(value: string) { setSortId(value); } - function handleSortOrder(value) { + function handleSortOrder(value: string) { setSortOrder(value); } @@ -243,9 +261,14 @@ const CharacterDirectoryList = (props) => { ); }; -const SortButton = (props) => { - const { act, data } = useBackend(); - +const SortButton = (props: { + id: string; + sortId: string; + sortOrder: string; + onSortId: Function; + onSortOrder: Function; + children: ReactNode | string; +}) => { const { id, children } = props; // Hey, same keys mean same data~ diff --git a/tgui/packages/tgui/interfaces/CheckboxInput.tsx b/tgui/packages/tgui/interfaces/CheckboxInput.tsx index 30ed63d08b..de6fe94eff 100644 --- a/tgui/packages/tgui/interfaces/CheckboxInput.tsx +++ b/tgui/packages/tgui/interfaces/CheckboxInput.tsx @@ -97,7 +97,7 @@ export const CheckboxInput = (props) => { setSearchQuery(value)} + onInput={(e, value: string) => setSearchQuery(value)} /> diff --git a/tgui/packages/tgui/interfaces/ChemDispenser.jsx b/tgui/packages/tgui/interfaces/ChemDispenser.jsx deleted file mode 100644 index 801fe33dd3..0000000000 --- a/tgui/packages/tgui/interfaces/ChemDispenser.jsx +++ /dev/null @@ -1,175 +0,0 @@ -import { useBackend } from '../backend'; -import { Box, Button, Flex, LabeledList, Section, Slider } from '../components'; -import { BeakerContents } from '../interfaces/common/BeakerContents'; -import { Window } from '../layouts'; - -const dispenseAmounts = [5, 10, 20, 30, 40, 60]; -const removeAmounts = [1, 5, 10]; - -export const ChemDispenser = (props) => { - return ( - - - - - - - - ); -}; - -const ChemDispenserSettings = (properties) => { - const { act, data } = useBackend(); - const { amount } = data; - return ( -
- - - {dispenseAmounts.map((a, i) => ( - - ))} - - - - act('amount', { - amount: value, - }) - } - /> - - -
- ); -}; - -const ChemDispenserChemicals = (properties) => { - const { act, data } = useBackend(); - const { chemicals = [] } = data; - const flexFillers = []; - for (let i = 0; i < (chemicals.length + 1) % 3; i++) { - flexFillers.push(true); - } - return ( -
- - {chemicals.map((c, i) => ( - - - - ))} - {flexFillers.map((_, i) => ( - - ))} - -
- ); -}; - -const ChemDispenserBeaker = (properties) => { - const { act, data } = useBackend(); - const { - isBeakerLoaded, - beakerCurrentVolume, - beakerMaxVolume, - beakerContents = [], - } = data; - return ( -
- {!!isBeakerLoaded && ( - - {beakerCurrentVolume} / {beakerMaxVolume} units - - )} - - - } - > - ( - <> - - {removeAmounts.map((a, i) => ( - - ))} - - - )} - /> -
- ); -}; diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserBeaker.tsx b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserBeaker.tsx new file mode 100644 index 0000000000..b9946802ac --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserBeaker.tsx @@ -0,0 +1,81 @@ +import { useBackend } from '../../backend'; +import { Box, Button, Section } from '../../components'; +import { BeakerContents } from '.././common/BeakerContents'; +import { removeAmounts } from './constants'; +import { Data } from './types'; + +export const ChemDispenserBeaker = (props) => { + const { act, data } = useBackend(); + const { + isBeakerLoaded, + beakerCurrentVolume, + beakerMaxVolume, + beakerContents = [], + } = data; + return ( +
+ {!!isBeakerLoaded && ( + + {beakerCurrentVolume} / {beakerMaxVolume} units + + )} + + + } + > + ( + <> + + {removeAmounts.map((a, i) => ( + + ))} + + + )} + /> +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserChemicals.tsx b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserChemicals.tsx new file mode 100644 index 0000000000..06160da65f --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserChemicals.tsx @@ -0,0 +1,41 @@ +import { useBackend } from '../../backend'; +import { Button, Flex, Section } from '../../components'; +import { Data } from './types'; + +export const ChemDispenserChemicals = (props) => { + const { act, data } = useBackend(); + const { chemicals = [] } = data; + const flexFillers: boolean[] = []; + for (let i = 0; i < (chemicals.length + 1) % 3; i++) { + flexFillers.push(true); + } + return ( +
+ + {chemicals.map((c, i) => ( + + + + ))} + {flexFillers.map((_, i) => ( + + ))} + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserSettings.tsx b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserSettings.tsx new file mode 100644 index 0000000000..2a55775822 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemDispenser/ChemDispenserSettings.tsx @@ -0,0 +1,46 @@ +import { useBackend } from '../../backend'; +import { Button, LabeledList, Section, Slider } from '../../components'; +import { dispenseAmounts } from './constants'; +import { Data } from './types'; + +export const ChemDispenserSettings = (props) => { + const { act, data } = useBackend(); + const { amount } = data; + return ( +
+ + + {dispenseAmounts.map((a, i) => ( + + ))} + + + + act('amount', { + amount: value, + }) + } + /> + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/constants.ts b/tgui/packages/tgui/interfaces/ChemDispenser/constants.ts new file mode 100644 index 0000000000..8555508f50 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemDispenser/constants.ts @@ -0,0 +1,2 @@ +export const dispenseAmounts: number[] = [5, 10, 20, 30, 40, 60]; +export const removeAmounts: number[] = [1, 5, 10]; diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/index.tsx b/tgui/packages/tgui/interfaces/ChemDispenser/index.tsx new file mode 100644 index 0000000000..9e47296c17 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemDispenser/index.tsx @@ -0,0 +1,16 @@ +import { Window } from '../../layouts'; +import { ChemDispenserBeaker } from './ChemDispenserBeaker'; +import { ChemDispenserChemicals } from './ChemDispenserChemicals'; +import { ChemDispenserSettings } from './ChemDispenserSettings'; + +export const ChemDispenser = (props) => { + return ( + + + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemDispenser/types.ts b/tgui/packages/tgui/interfaces/ChemDispenser/types.ts new file mode 100644 index 0000000000..f558816663 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemDispenser/types.ts @@ -0,0 +1,13 @@ +import { BooleanLike } from 'common/react'; + +export type Data = { + amount: number; + isBeakerLoaded: BooleanLike; + glass: BooleanLike; + beakerContents: reagent[]; + beakerCurrentVolume: number | null; + beakerMaxVolume: number | null; + chemicals: reagent[]; +}; + +type reagent = { name: string; id: string; volume: number }; diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterAnalyzeModalBodyOverride.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterAnalyzeModalBodyOverride.tsx new file mode 100644 index 0000000000..f51100d3e1 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterAnalyzeModalBodyOverride.tsx @@ -0,0 +1,53 @@ +import { useBackend } from '../../backend'; +import { Box, Button, LabeledList, Section } from '../../components'; +import { Data, modalData } from './types'; + +export const analyzeModalBodyOverride = (modal: modalData) => { + const { act, data } = useBackend(); + const result = modal.args.analysis; + return ( +
+ + + {result.name} + + {(result.desc || '').length > 0 ? result.desc : 'N/A'} + + {result.blood_type && ( + <> + + {result.blood_type} + + + {result.blood_dna} + + + )} + {!data.condi && ( + + )} + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBeaker.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBeaker.tsx new file mode 100644 index 0000000000..e42b4a3207 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBeaker.tsx @@ -0,0 +1,94 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../../backend'; +import { Box, Button, Section } from '../../components'; +import { BeakerContents } from '.././common/BeakerContents'; +import { modalOpen } from '.././common/ComplexModal'; +import { transferAmounts } from './constants'; +import { reagent } from './types'; + +export const ChemMasterBeaker = (props: { + beaker: BooleanLike; + beakerReagents: reagent[]; + bufferNonEmpty: BooleanLike; +}) => { + const { act } = useBackend(); + const { beaker, beakerReagents, bufferNonEmpty } = props; + + let headerButton = bufferNonEmpty ? ( + act('eject')} + > + Eject and Clear Buffer + + ) : ( + + ); + + return ( +
+ {beaker ? ( + ( + + + {transferAmounts.map((am, j) => ( + + ))} + + + + )} + /> + ) : ( + No beaker loaded. + )} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBuffer.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBuffer.tsx new file mode 100644 index 0000000000..3bd477b685 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterBuffer.tsx @@ -0,0 +1,92 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../../backend'; +import { Box, Button, Section } from '../../components'; +import { BeakerContents } from '.././common/BeakerContents'; +import { modalOpen } from '.././common/ComplexModal'; +import { transferAmounts } from './constants'; +import { reagent } from './types'; + +export const ChemMasterBuffer = (props: { + mode: BooleanLike; + bufferReagents: reagent[]; +}) => { + const { act } = useBackend(); + const { mode, bufferReagents = [] } = props; + return ( +
+ Transferring to  + + + } + > + {bufferReagents.length > 0 ? ( + ( + + + {transferAmounts.map((am, i) => ( + + ))} + + + + )} + /> + ) : ( + Buffer is empty. + )} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterCustomization.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterCustomization.tsx new file mode 100644 index 0000000000..3e70da5f8e --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterCustomization.tsx @@ -0,0 +1,55 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../../backend'; +import { Box, Button, Section } from '../../components'; +import { modalOpen } from '.././common/ComplexModal'; + +export const ChemMasterCustomization = (props: { + loaded_pill_bottle: BooleanLike; + loaded_pill_bottle_name: string; + loaded_pill_bottle_contents_len: number; + loaded_pill_bottle_storage_slots: number; +}) => { + const { act } = useBackend(); + + const { + loaded_pill_bottle, + loaded_pill_bottle_name, + loaded_pill_bottle_contents_len, + loaded_pill_bottle_storage_slots, + } = props; + + if (!loaded_pill_bottle) { + return ( +
+ None loaded. +
+ ); + } + + return ( +
+ + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProduction.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProduction.tsx new file mode 100644 index 0000000000..59f9619c64 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProduction.tsx @@ -0,0 +1,97 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../../backend'; +import { Button, Flex, Icon, Section } from '../../components'; +import { ChemMasterProductionChemical } from './ChemMasterProductionChemical'; +import { ChemMasterProductionCondiment } from './ChemMasterProductionCondiment'; + +export const ChemMasterProduction = (props: { + bufferNonEmpty: boolean; + isCondiment: BooleanLike; + loaded_pill_bottle: BooleanLike; + loaded_pill_bottle_name: string; + loaded_pill_bottle_contents_len: number; + loaded_pill_bottle_storage_slots: number; + pillsprite: string; + bottlesprite: string; +}) => { + const { act } = useBackend(); + + const { + bufferNonEmpty, + isCondiment, + loaded_pill_bottle, + loaded_pill_bottle_name, + loaded_pill_bottle_contents_len, + loaded_pill_bottle_storage_slots, + pillsprite, + bottlesprite, + } = props; + + if (!bufferNonEmpty) { + return ( +
act('ejectp')} + > + {loaded_pill_bottle + ? loaded_pill_bottle_name + + ' (' + + loaded_pill_bottle_contents_len + + '/' + + loaded_pill_bottle_storage_slots + + ')' + : 'No pill bottle loaded'} + + } + > + + + +
+ Buffer is empty. +
+
+
+ ); + } + + return ( +
act('ejectp')} + > + {loaded_pill_bottle + ? loaded_pill_bottle_name + + ' (' + + loaded_pill_bottle_contents_len + + '/' + + loaded_pill_bottle_storage_slots + + ')' + : 'No pill bottle loaded'} + + } + > + {!isCondiment ? ( + + ) : ( + + )} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProductionChemical.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProductionChemical.tsx new file mode 100644 index 0000000000..3b7a4c2e13 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProductionChemical.tsx @@ -0,0 +1,111 @@ +import { classes } from '../../.././common/react'; +import { Box, Button, LabeledList } from '../../components'; +import { modalOpen } from '.././common/ComplexModal'; + +export const ChemMasterProductionChemical = (props: { + pillsprite: string; + bottlesprite: string; +}) => { + const { pillsprite, bottlesprite } = props; + + return ( + + + + +
+ +
+ + + + + + + +
+ +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProductionCondiment.tsx b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProductionCondiment.tsx new file mode 100644 index 0000000000..fd58ec8228 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemMaster/ChemMasterProductionCondiment.tsx @@ -0,0 +1,26 @@ +import { useBackend } from '../../backend'; +import { Button } from '../../components'; +import { modalOpen } from '.././common/ComplexModal'; + +export const ChemMasterProductionCondiment = (props) => { + const { act } = useBackend(); + return ( + <> + +
+ + + ); +}; diff --git a/tgui/packages/tgui/interfaces/ChemMaster/constants.ts b/tgui/packages/tgui/interfaces/ChemMaster/constants.ts new file mode 100644 index 0000000000..8fa1d18fa3 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemMaster/constants.ts @@ -0,0 +1,8 @@ +export const transferAmounts: number[] = [1, 5, 10, 30, 60]; +export const bottleStyles: string[] = [ + 'bottle.png', + 'small_bottle.png', + 'wide_bottle.png', + 'round_bottle.png', + 'reagent_bottle.png', +]; diff --git a/tgui/packages/tgui/interfaces/ChemMaster/index.tsx b/tgui/packages/tgui/interfaces/ChemMaster/index.tsx new file mode 100644 index 0000000000..658ffc8f3c --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemMaster/index.tsx @@ -0,0 +1,65 @@ +import { useBackend } from '../../backend'; +import { Window } from '../../layouts'; +import { + ComplexModal, + modalRegisterBodyOverride, +} from '.././common/ComplexModal'; +import { analyzeModalBodyOverride } from './ChemMasterAnalyzeModalBodyOverride'; +import { ChemMasterBeaker } from './ChemMasterBeaker'; +import { ChemMasterBuffer } from './ChemMasterBuffer'; +import { ChemMasterProduction } from './ChemMasterProduction'; +import { Data } from './types'; + +export const ChemMaster = (props) => { + const { data } = useBackend(); + const { + condi, + beaker, + beaker_reagents = [], + buffer_reagents = [], + mode, + loaded_pill_bottle, + loaded_pill_bottle_name, + loaded_pill_bottle_contents_len, + loaded_pill_bottle_storage_slots, + pillsprite, + bottlesprite, + } = data; + return ( + + + + 0} + /> + + 0} + loaded_pill_bottle={loaded_pill_bottle} + loaded_pill_bottle_name={loaded_pill_bottle_name || ''} + loaded_pill_bottle_contents_len={loaded_pill_bottle_contents_len || 0} + loaded_pill_bottle_storage_slots={ + loaded_pill_bottle_storage_slots || 0 + } + pillsprite={pillsprite} + bottlesprite={bottlesprite} + /> + {/* Vorestation Remove + + */} + + + ); +}; + +modalRegisterBodyOverride('analyze', analyzeModalBodyOverride); diff --git a/tgui/packages/tgui/interfaces/ChemMaster/types.ts b/tgui/packages/tgui/interfaces/ChemMaster/types.ts new file mode 100644 index 0000000000..a5b6dcdca0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ChemMaster/types.ts @@ -0,0 +1,40 @@ +import { BooleanLike } from 'common/react'; + +export type Data = { + condi: BooleanLike; + loaded_pill_bottle: BooleanLike; + loaded_pill_bottle_name: string | undefined; + loaded_pill_bottle_contents_len: number | undefined; + loaded_pill_bottle_storage_slots: number | undefined; + beaker: BooleanLike; + beaker_reagents: reagent[] | undefined; + buffer_reagents: reagent[] | undefined; + pillsprite: string; + bottlesprite: string; + mode: BooleanLike; + printing: BooleanLike; + modal: modalData; +}; + +export type reagent = { + name: string; + volume: number; + description: string; + id: string; +}; + +export type modalData = { + id: string; + text: string; + args: { + analysis: { + idx: string; + name: string; + desc: string; + blood_type: string | null; + blood_dna: string | null; + }; + beaker: number; + }; + type: string; +}; diff --git a/tgui/packages/tgui/interfaces/ClawMachine.jsx b/tgui/packages/tgui/interfaces/ClawMachine.tsx similarity index 91% rename from tgui/packages/tgui/interfaces/ClawMachine.jsx rename to tgui/packages/tgui/interfaces/ClawMachine.tsx index a25e9e0395..3b43c19352 100644 --- a/tgui/packages/tgui/interfaces/ClawMachine.jsx +++ b/tgui/packages/tgui/interfaces/ClawMachine.tsx @@ -2,8 +2,15 @@ import { useBackend } from '../backend'; import { Box, Button, LabeledList, ProgressBar } from '../components'; import { Window } from '../layouts'; +type Data = { + wintick: number; + instructions: string; + gameStatus: string; + winscreen: string; +}; + export const ClawMachine = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { wintick, instructions, gameStatus, winscreen } = data; let body; @@ -39,7 +46,7 @@ export const ClawMachine = (props) => { average: [1, 7], good: [8, Infinity], }} - value={data.wintick} + value={wintick} minValue={0} maxValue={10} /> diff --git a/tgui/packages/tgui/interfaces/Cleanbot.jsx b/tgui/packages/tgui/interfaces/Cleanbot.tsx similarity index 91% rename from tgui/packages/tgui/interfaces/Cleanbot.jsx rename to tgui/packages/tgui/interfaces/Cleanbot.tsx index 09632130d3..e66624a11e 100644 --- a/tgui/packages/tgui/interfaces/Cleanbot.jsx +++ b/tgui/packages/tgui/interfaces/Cleanbot.tsx @@ -1,9 +1,27 @@ +import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; import { Box, Button, LabeledList, Section } from '../components'; import { Window } from '../layouts'; +type Data = { + on: BooleanLike; + open: BooleanLike; + locked: BooleanLike; + patrol: BooleanLike; + vocal: BooleanLike; + wet_floors: BooleanLike; + spray_blood: BooleanLike; + version: string; + blood: BooleanLike; + rgbpanel: BooleanLike; + red_switch: BooleanLike; + green_switch: BooleanLike; + blue_switch: BooleanLike; +}; + export const Cleanbot = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { on, @@ -11,7 +29,6 @@ export const Cleanbot = (props) => { locked, version, blood, - patrol, vocal, wet_floors, spray_blood, diff --git a/tgui/packages/tgui/interfaces/CloningConsole.jsx b/tgui/packages/tgui/interfaces/CloningConsole.jsx deleted file mode 100644 index 2e6d93e784..0000000000 --- a/tgui/packages/tgui/interfaces/CloningConsole.jsx +++ /dev/null @@ -1,458 +0,0 @@ -import { toFixed } from 'common/math'; - -import { resolveAsset } from '../assets'; -import { useBackend } from '../backend'; -import { - Box, - Button, - Flex, - Icon, - LabeledList, - NoticeBox, - ProgressBar, - Section, - Tabs, -} from '../components'; -import { COLORS } from '../constants'; -import { - ComplexModal, - modalRegisterBodyOverride, -} from '../interfaces/common/ComplexModal'; -import { Window } from '../layouts'; - -const viewRecordModalBodyOverride = (modal) => { - const { act, data } = useBackend(); - const { activerecord, realname, health, unidentity, strucenzymes } = - modal.args; - const damages = health.split(' - '); - return ( -
- - {realname} - - {damages.length > 1 ? ( - <> - - {damages[0]} - -  |  - - {damages[2]} - -  |  - - {damages[3]} - -  |  - - {damages[1]} - - - ) : ( - Unknown - )} - - - {unidentity} - - - {strucenzymes} - - - - act('disk', { - option: 'load', - }) - } - > - Import - - - - - - - - - - -
- ); -}; - -export const CloningConsole = (props) => { - const { act, data } = useBackend(); - const { menu } = data; - modalRegisterBodyOverride('view_rec', viewRecordModalBodyOverride); - return ( - - - - - - -
- -
-
-
- ); -}; - -const CloningConsoleNavigation = (props) => { - const { act, data } = useBackend(); - const { menu } = data; - return ( - - - act('menu', { - num: 1, - }) - } - > - Main - - - act('menu', { - num: 2, - }) - } - > - Records - - - ); -}; - -const CloningConsoleBody = (props) => { - const { data } = useBackend(); - const { menu } = data; - let body; - if (menu === 1) { - body = ; - } else if (menu === 2) { - body = ; - } - return body; -}; - -const CloningConsoleMain = (props) => { - const { act, data } = useBackend(); - const { - loading, - scantemp, - occupant, - locked, - can_brainscan, - scan_mode, - numberofpods, - pods, - selected_pod, - } = data; - const isLocked = locked && !!occupant; - return ( - <> -
- - Scanner Lock:  - - - - - } - > - - - {loading ? ( - - -   Scanning... - - ) : ( - {scantemp.text} - )} - - {!!can_brainscan && ( - - - - )} - - -
-
- {numberofpods ? ( - pods.map((pod, i) => { - let podAction; - if (pod.status === 'cloning') { - podAction = ( - - {toFixed(pod.progress) + '%'} - - ); - } else if (pod.status === 'mess') { - podAction = ( - - ERROR - - ); - } else { - podAction = ( - - ); - } - - return ( - - - Pod #{i + 1} - = 150 ? 'good' : 'bad'} inline> - = 150 ? 'circle' : 'circle-o'} /> -   - {pod.biomass} - - {podAction} - - ); - }) - ) : ( - No pods detected. Unable to clone. - )} -
- - ); -}; - -const CloningConsoleRecords = (props) => { - const { act, data } = useBackend(); - const { records } = data; - if (!records.length) { - return ( - - - -
- No records found. -
-
- ); - } - return ( - - {records.map((record, i) => ( - - ))} - - ); -}; - -const CloningConsoleTemp = (props) => { - const { act, data } = useBackend(); - const { temp } = data; - if (!temp || !temp.text || temp.text.length <= 0) { - return; - } - - const tempProp = { [temp.style]: true }; - return ( - - - {temp.text} - - - - )} - - - } - > - - - {scanner ? ( - Connected - ) : ( - Not connected! - )} - - - {numberofpods ? ( - {numberofpods} connected - ) : ( - None connected! - )} - - -
- ); -}; diff --git a/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleBodyOverride.tsx b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleBodyOverride.tsx new file mode 100644 index 0000000000..fb6a649152 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleBodyOverride.tsx @@ -0,0 +1,115 @@ +import { useBackend } from '../../backend'; +import { Box, Button, LabeledList, Section } from '../../components'; +import { COLORS } from '../../constants'; +import { Data, modalData } from './types'; + +export const viewRecordModalBodyOverride = (modal: modalData) => { + const { act, data } = useBackend(); + + const { disk, podready } = data; + + const { activerecord, realname, health, unidentity, strucenzymes } = + modal.args; + const damages = health.split(' - '); + return ( +
+ + {realname} + + {damages.length > 1 ? ( + <> + + {damages[0]} + +  |  + + {damages[2]} + +  |  + + {damages[3]} + +  |  + + {damages[1]} + + + ) : ( + Unknown + )} + + + {unidentity} + + + {strucenzymes} + + + + act('disk', { + option: 'load', + }) + } + > + Import + + + + + + + + + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleNavigation.tsx b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleNavigation.tsx new file mode 100644 index 0000000000..d06866ca50 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleNavigation.tsx @@ -0,0 +1,34 @@ +import { useBackend } from '../../backend'; +import { Tabs } from '../../components'; +import { Data } from './types'; + +export const CloningConsoleNavigation = (props) => { + const { act, data } = useBackend(); + const { menu } = data; + return ( + + + act('menu', { + num: 1, + }) + } + > + Main + + + act('menu', { + num: 2, + }) + } + > + Records + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleStatus.tsx b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleStatus.tsx new file mode 100644 index 0000000000..4034d3fed6 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleStatus.tsx @@ -0,0 +1,92 @@ +import { useBackend } from '../../backend'; +import { Box, Button, LabeledList, NoticeBox, Section } from '../../components'; +import { Data } from './types'; + +export const CloningConsoleTemp = (props) => { + const { act, data } = useBackend(); + const { temp } = data; + if (!temp || !temp.text || temp.text.length <= 0) { + return; + } + + const tempProp = { [temp.style]: true }; + return ( + + + {temp.text} + + + + )} + + + } + > + + + {scanner ? ( + Connected + ) : ( + Not connected! + )} + + + {numberofpods ? ( + {numberofpods} connected + ) : ( + None connected! + )} + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleTabs.tsx b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleTabs.tsx new file mode 100644 index 0000000000..4399d2a3b5 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CloningConsole/CloningConsoleTabs.tsx @@ -0,0 +1,192 @@ +import { toFixed } from 'common/math'; + +import { resolveAsset } from '../../assets'; +import { useBackend } from '../../backend'; +import { + Box, + Button, + Flex, + Icon, + Image, + LabeledList, + ProgressBar, + Section, +} from '../../components'; +import { Data } from './types'; + +export const CloningConsoleMain = (props) => { + const { act, data } = useBackend(); + const { + loading, + scantemp, + occupant, + locked, + can_brainscan, + scan_mode, + numberofpods, + pods, + selected_pod, + } = data; + const isLocked = locked && !!occupant; + return ( + <> +
+ + Scanner Lock:  + + + + + } + > + + + {loading ? ( + + +   Scanning... + + ) : ( + {scantemp.text} + )} + + {!!can_brainscan && ( + + + + )} + + +
+
+ {numberofpods ? ( + pods.map((pod, i) => { + let podAction; + if (pod.status === 'cloning') { + podAction = ( + + {toFixed(pod.progress) + '%'} + + ); + } else if (pod.status === 'mess') { + podAction = ( + + ERROR + + ); + } else { + podAction = ( + + ); + } + + return ( + + + Pod #{i + 1} + = 150 ? 'good' : 'bad'} inline> + = 150 ? 'circle' : 'circle-o'} /> +   + {pod.biomass} + + {podAction} + + ); + }) + ) : ( + No pods detected. Unable to clone. + )} +
+ + ); +}; + +export const CloningConsoleRecords = (props) => { + const { act, data } = useBackend(); + const { records } = data; + if (!records.length) { + return ( + + + +
+ No records found. +
+
+ ); + } + return ( + + {records.map((record, i) => ( + + ))} + + ); +}; diff --git a/tgui/packages/tgui/interfaces/CloningConsole/index.tsx b/tgui/packages/tgui/interfaces/CloningConsole/index.tsx new file mode 100644 index 0000000000..7957732fd7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CloningConsole/index.tsx @@ -0,0 +1,44 @@ +import { useBackend } from '../../backend'; +import { Box, Section } from '../../components'; +import { + ComplexModal, + modalRegisterBodyOverride, +} from '../../interfaces/common/ComplexModal'; +import { Window } from '../../layouts'; +import { viewRecordModalBodyOverride } from './CloningConsoleBodyOverride'; +import { CloningConsoleNavigation } from './CloningConsoleNavigation'; +import { + CloningConsoleStatus, + CloningConsoleTemp, +} from './CloningConsoleStatus'; +import { + CloningConsoleMain, + CloningConsoleRecords, +} from './CloningConsoleTabs'; +import { Data } from './types'; + +export const CloningConsole = (props) => { + const { data } = useBackend(); + + const { menu } = data; + + const tab: React.JSX.Element[] = []; + + tab[1] = ; + tab[2] = ; + + modalRegisterBodyOverride('view_rec', viewRecordModalBodyOverride); + return ( + + + + + + +
+ {tab[menu] || Error} +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/CloningConsole/types.ts b/tgui/packages/tgui/interfaces/CloningConsole/types.ts new file mode 100644 index 0000000000..5621fd1055 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CloningConsole/types.ts @@ -0,0 +1,41 @@ +import { BooleanLike } from 'common/react'; + +export type Data = { + menu: number; + scanner: string; + numberofpods: number | null; + pods: { + pod: string; + name: string; + biomass: number; + status: string; + progress: number; + }[]; + loading: BooleanLike; + autoprocess: BooleanLike; + can_brainscan: BooleanLike; + scan_mode: BooleanLike; + autoallowed: BooleanLike; + occupant: string; + locked: BooleanLike; + temp: { text: string; style: string }; + scantemp: { text: string; color: string }; + disk: string | null; + selected_pod: string; + records: { record: string; realname: string }[]; + podready: BooleanLike; + modal: modalData; +}; + +export type modalData = { + id: string; + text: string; + args: { + activerecord: string; + realname: string; + health: string; + unidentity: string; + strucenzymes: string; + }; + modal_type: string; +}; diff --git a/tgui/packages/tgui/interfaces/ColorMate.jsx b/tgui/packages/tgui/interfaces/ColorMate.jsx deleted file mode 100644 index 8e1f0814ee..0000000000 --- a/tgui/packages/tgui/interfaces/ColorMate.jsx +++ /dev/null @@ -1,418 +0,0 @@ -import { toFixed } from 'common/math'; - -import { useBackend } from '../backend'; -import { - Button, - Icon, - NoticeBox, - NumberInput, - Section, - Slider, - Table, - Tabs, -} from '../components'; -import { Window } from '../layouts'; - -export const ColorMate = (props, context) => { - const { act, data } = useBackend(context); - const { activemode, temp } = data; - const item = data.item || []; - return ( - - -
- {temp ? {temp} : null} - {Object.keys(item).length ? ( - <> - - -
-
Item:
- -
-
- -
-
Preview:
- -
-
-
- - - act('switch_modes', { - mode: 1, - }) - } - > - Tint coloring (Simple) - - - act('switch_modes', { - mode: 2, - }) - } - > - HSV coloring (Normal) - - - act('switch_modes', { - mode: 3, - }) - } - > - Matrix coloring (Advanced) - - -
Coloring: {item.name}
- - - - - - - - {activemode === 1 ? ( - - ) : activemode === 2 ? ( - - ) : ( - - )} - -
- - ) : ( -
No item inserted.
- )} -
-
-
- ); -}; - -export const ColorMateTint = (props, context) => { - const { act, data } = useBackend(context); - return ( - - ); -}; - -export const ColorMateMatrix = (props, context) => { - const { act, data } = useBackend(context); - const matrixcolors = data.matrixcolors || []; - return ( - - - - RR:{' '} - toFixed(value, 2)} - onChange={(e, value) => - act('set_matrix_color', { - color: 1, - value, - }) - } - /> - - - GR:{' '} - toFixed(value, 2)} - onChange={(e, value) => - act('set_matrix_color', { - color: 4, - value, - }) - } - /> - - - BR:{' '} - toFixed(value, 2)} - onChange={(e, value) => - act('set_matrix_color', { - color: 7, - value, - }) - } - /> - - - - - RG:{' '} - toFixed(value, 2)} - onChange={(e, value) => - act('set_matrix_color', { - color: 2, - value, - }) - } - /> - - - GG:{' '} - toFixed(value, 2)} - onChange={(e, value) => - act('set_matrix_color', { - color: 5, - value, - }) - } - /> - - - BG:{' '} - toFixed(value, 2)} - onChange={(e, value) => - act('set_matrix_color', { - color: 8, - value, - }) - } - /> - - - - - RB:{' '} - toFixed(value, 2)} - onChange={(e, value) => - act('set_matrix_color', { - color: 3, - value, - }) - } - /> - - - GB:{' '} - toFixed(value, 2)} - onChange={(e, value) => - act('set_matrix_color', { - color: 6, - value, - }) - } - /> - - - BB:{' '} - toFixed(value, 2)} - onChange={(e, value) => - act('set_matrix_color', { - color: 9, - value, - }) - } - /> - - - - - CR:{' '} - toFixed(value, 2)} - onChange={(e, value) => - act('set_matrix_color', { - color: 10, - value, - }) - } - /> - - - CG:{' '} - toFixed(value, 2)} - onChange={(e, value) => - act('set_matrix_color', { - color: 11, - value, - }) - } - /> - - - CB:{' '} - toFixed(value, 2)} - onChange={(e, value) => - act('set_matrix_color', { - color: 12, - value, - }) - } - /> - - - - RG means red will become - this much green. -
- CR means this much red will - be added. -
-
- ); -}; - -export const ColorMateHSV = (props, context) => { - const { act, data } = useBackend(context); - const { buildhue, buildsat, buildval } = data; - return ( - - -
Hue:
- - toFixed(value)} - onDrag={(e, value) => - act('set_hue', { - buildhue: value, - }) - } - /> - -
- -
Saturation:
- - toFixed(value, 2)} - onDrag={(e, value) => - act('set_sat', { - buildsat: value, - }) - } - /> - -
- -
Value:
- - toFixed(value, 2)} - onDrag={(e, value) => - act('set_val', { - buildval: value, - }) - } - /> - -
-
- ); -}; diff --git a/tgui/packages/tgui/interfaces/ColorMate/ColorMateColor.tsx b/tgui/packages/tgui/interfaces/ColorMate/ColorMateColor.tsx new file mode 100644 index 0000000000..a5efd4024a --- /dev/null +++ b/tgui/packages/tgui/interfaces/ColorMate/ColorMateColor.tsx @@ -0,0 +1,76 @@ +import { toFixed } from 'common/math'; + +import { useBackend } from '../../backend'; +import { Button, Slider, Table } from '../../components'; +import { Data } from './types'; + +export const ColorMateTint = (props) => { + const { act } = useBackend(); + + return ( + + ); +}; + +export const ColorMateHSV = (props) => { + const { act, data } = useBackend(); + + const { buildhue, buildsat, buildval } = data; + return ( + + +
Hue:
+ + toFixed(value)} + onDrag={(e, value: number) => + act('set_hue', { + buildhue: value, + }) + } + /> + +
+ +
Saturation:
+ + toFixed(value, 2)} + onDrag={(e, value: number) => + act('set_sat', { + buildsat: value, + }) + } + /> + +
+ +
Value:
+ + toFixed(value, 2)} + onDrag={(e, value: number) => + act('set_val', { + buildval: value, + }) + } + /> + +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ColorMate/ColorMateMatrix.tsx b/tgui/packages/tgui/interfaces/ColorMate/ColorMateMatrix.tsx new file mode 100644 index 0000000000..c9c0c5fc0d --- /dev/null +++ b/tgui/packages/tgui/interfaces/ColorMate/ColorMateMatrix.tsx @@ -0,0 +1,235 @@ +import { toFixed } from 'common/math'; + +import { useBackend } from '../../backend'; +import { Icon, NumberInput, Table } from '../../components'; +import { Data } from './types'; + +export const ColorMateMatrix = (props) => { + const { act, data } = useBackend(); + + const { matrixcolors } = data; + + return ( + + + + RR: + toFixed(value, 2)} + onChange={(e, value: number) => + act('set_matrix_color', { + color: 1, + value, + }) + } + /> + + + GR: + toFixed(value, 2)} + onChange={(e, value: number) => + act('set_matrix_color', { + color: 4, + value, + }) + } + /> + + + BR: + toFixed(value, 2)} + onChange={(e, value: number) => + act('set_matrix_color', { + color: 7, + value, + }) + } + /> + + + + + RG: + toFixed(value, 2)} + onChange={(e, value: number) => + act('set_matrix_color', { + color: 2, + value, + }) + } + /> + + + GG: + toFixed(value, 2)} + onChange={(e, value: number) => + act('set_matrix_color', { + color: 5, + value, + }) + } + /> + + + BG: + toFixed(value, 2)} + onChange={(e, value: number) => + act('set_matrix_color', { + color: 8, + value, + }) + } + /> + + + + + RB: + toFixed(value, 2)} + onChange={(e, value: number) => + act('set_matrix_color', { + color: 3, + value, + }) + } + /> + + + GB: + toFixed(value, 2)} + onChange={(e, value: number) => + act('set_matrix_color', { + color: 6, + value, + }) + } + /> + + + BB: + toFixed(value, 2)} + onChange={(e, value: number) => + act('set_matrix_color', { + color: 9, + value, + }) + } + /> + + + + + CR: + toFixed(value, 2)} + onChange={(e, value: number) => + act('set_matrix_color', { + color: 10, + value, + }) + } + /> + + + CG: + toFixed(value, 2)} + onChange={(e, value: number) => + act('set_matrix_color', { + color: 11, + value, + }) + } + /> + + + CB: + toFixed(value, 2)} + onChange={(e, value: number) => + act('set_matrix_color', { + color: 12, + value, + }) + } + /> + + + + RG means red will become + this much green. +
+ CR means this much red will + be added. +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ColorMate/index.tsx b/tgui/packages/tgui/interfaces/ColorMate/index.tsx new file mode 100644 index 0000000000..8bbe62e705 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ColorMate/index.tsx @@ -0,0 +1,120 @@ +import { useBackend } from '../../backend'; +import { + Box, + Button, + Image, + NoticeBox, + Section, + Table, + Tabs, +} from '../../components'; +import { Window } from '../../layouts'; +import { ColorMateHSV, ColorMateTint } from './ColorMateColor'; +import { ColorMateMatrix } from './ColorMateMatrix'; +import { Data } from './types'; + +export const ColorMate = (props) => { + const { act, data } = useBackend(); + + const { activemode, temp, item } = data; + + const tab: React.JSX.Element[] = []; + + tab[1] = ; + tab[2] = ; + tab[3] = ; + + return ( + + +
+ {temp ? {temp} : null} + {item && Object.keys(item).length ? ( + <> + + +
+
Item:
+ +
+
+ +
+
Preview:
+ +
+
+
+ + + act('switch_modes', { + mode: 1, + }) + } + > + Tint coloring (Simple) + + + act('switch_modes', { + mode: 2, + }) + } + > + HSV coloring (Normal) + + + act('switch_modes', { + mode: 3, + }) + } + > + Matrix coloring (Advanced) + + +
Coloring: {item.name}
+ + + + + + + + {tab[activemode] || Error} + +
+ + ) : ( +
No item inserted.
+ )} +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ColorMate/types.ts b/tgui/packages/tgui/interfaces/ColorMate/types.ts new file mode 100644 index 0000000000..a56bab9c7b --- /dev/null +++ b/tgui/packages/tgui/interfaces/ColorMate/types.ts @@ -0,0 +1,22 @@ +export type Data = { + activemode: number; + matrixcolors: { + rr: number; + rg: number; + rb: number; + gr: number; + gg: number; + gb: number; + br: number; + bg: number; + bb: number; + cr: number; + cg: number; + cb: number; + }; + buildhue: number; + buildsat: number; + buildval: number; + temp: string | null; + item: { name: string; sprite: string; preview: string } | null; +}; diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx b/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx deleted file mode 100644 index 857364e55d..0000000000 --- a/tgui/packages/tgui/interfaces/CommunicationsConsole.jsx +++ /dev/null @@ -1,364 +0,0 @@ -import { useBackend } from '../backend'; -import { Box, Button, LabeledList, Section } from '../components'; -import { Window } from '../layouts'; - -export const CommunicationsConsole = (props) => { - return ( - - - - - - ); -}; - -export const CommunicationsConsoleContent = (props) => { - const { act, data } = useBackend(); - - const { menu_state } = data; - - let mainTemplate = ( - - ERRROR. Unknown menu_state: {menu_state} - Please report this to NT Technical Support. - - ); - - // 1 = main screen - if (menu_state === 1) { - mainTemplate = ; - } else if (menu_state === 2) { - // 2 = status screen - mainTemplate = ; - } else if (menu_state === 3) { - // 3 = messages screen - mainTemplate = ; - } - - return ( - <> - - {mainTemplate} - - ); -}; - -const CommunicationsConsoleMain = (props) => { - const { act, data } = useBackend(); - - const { - messages, - msg_cooldown, - emagged, - cc_cooldown, - str_security_level, - levels, - authmax, - security_level, - security_level_color, - authenticated, - atcsquelch, - boss_short, - } = data; - - let reportText = 'View (' + messages.length + ')'; - let announceText = 'Make Priority Announcement'; - if (msg_cooldown > 0) { - announceText += ' (' + msg_cooldown + 's)'; - } - let ccMessageText = emagged ? 'Message [UNKNOWN]' : 'Message ' + boss_short; - if (cc_cooldown > 0) { - ccMessageText += ' (' + cc_cooldown + 's)'; - } - - let alertLevelText = str_security_level; - let alertLevelButtons = levels.map((slevel) => { - return ( - - ); - }); - - return ( - <> -
- - - - - {(!!emagged && ( - - - - - )) || ( - - - - )} - -
-
- - - {alertLevelText} - - - {alertLevelButtons} - - - - - - - - - - - -
- - ); -}; - -const CommunicationsConsoleAuth = (props) => { - const { act, data } = useBackend(); - - const { authenticated, is_ai, esc_status, esc_callable, esc_recallable } = - data; - - let authReadable; - if (!authenticated) { - authReadable = 'Not Logged In'; - } else if (is_ai) { - authReadable = 'AI'; - } else if (authenticated === 1) { - authReadable = 'Command'; - } else if (authenticated === 2) { - authReadable = 'Site Director'; - } else { - authReadable = 'ERROR: Report This Bug!'; - } - - return ( - <> -
- - {(is_ai && ( - AI - )) || ( - - - - )} - -
-
- - {!!esc_status && ( - {esc_status} - )} - {!!esc_callable && ( - - - - )} - {!!esc_recallable && ( - - - - )} - -
- - ); -}; - -const CommunicationsConsoleMessage = (props) => { - const { act, data } = useBackend(); - - const { message_current, message_deletion_allowed, authenticated, messages } = - data; - - if (message_current) { - return ( -
act('messagelist')} - > - Return To Message List - - } - > - {message_current.contents} -
- ); - } - - let messageRows = messages.map((m) => { - return ( - - - - - ); - }); - - return ( -
act('main')}> - Back To Main Menu - - } - > - - {(messages.length && messageRows) || ( - - No messages. - - )} - -
- ); -}; - -const CommunicationsConsoleStatusDisplay = (props) => { - const { act, data } = useBackend(); - - const { stat_display, authenticated } = data; - - let presetButtons = stat_display['presets'].map((pb) => { - return ( - - ); - }); - return ( -
act('main')}> - Back To Main Menu - - } - > - - {presetButtons} - - - - - - - -
- ); -}; diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleAuth.tsx b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleAuth.tsx new file mode 100644 index 0000000000..24b814ebee --- /dev/null +++ b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleAuth.tsx @@ -0,0 +1,74 @@ +import { useBackend } from '../../backend'; +import { Button, LabeledList, Section } from '../../components'; +import { Data } from './types'; + +export const CommunicationsConsoleAuth = (props) => { + const { act, data } = useBackend(); + + const { authenticated, is_ai, esc_status, esc_callable, esc_recallable } = + data; + + let authReadable; + if (!authenticated) { + authReadable = 'Not Logged In'; + } else if (is_ai) { + authReadable = 'AI'; + } else if (authenticated === 1) { + authReadable = 'Command'; + } else if (authenticated === 2) { + authReadable = 'Site Director'; + } else { + authReadable = 'ERROR: Report This Bug!'; + } + + return ( + <> +
+ + {(is_ai && ( + AI + )) || ( + + + + )} + +
+
+ + {!!esc_status && ( + {esc_status} + )} + {!!esc_callable && ( + + + + )} + {!!esc_recallable && ( + + + + )} + +
+ + ); +}; diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleContent.tsx b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleContent.tsx new file mode 100644 index 0000000000..57826b88f3 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleContent.tsx @@ -0,0 +1,37 @@ +import { useBackend } from '../../backend'; +import { Box } from '../../components'; +import { CommunicationsConsoleAuth } from './CommunicationsConsoleAuth'; +import { CommunicationsConsoleMain } from './CommunicationsConsoleMain'; +import { CommunicationsConsoleMessage } from './CommunicationsConsoleMessage'; +import { CommunicationsConsoleStatusDisplay } from './CommunicationsConsoleStatusDisplay'; +import { Data } from './types'; + +export const CommunicationsConsoleContent = (props) => { + const { data } = useBackend(); + + const { menu_state } = data; + + const tab: React.JSX.Element[] = []; + + tab[1] = ; + tab[2] = ; + tab[3] = ; + + return ( + <> + + {tab[menu_state] || } + + ); +}; + +const DefaultError = (props: { menu_state: string }) => { + const { menu_state } = props; + + return ( + + ERRROR. Unknown menu_state: {menu_state} + Please report this to NT Technical Support. + + ); +}; diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleMain.tsx b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleMain.tsx new file mode 100644 index 0000000000..caa937b1fc --- /dev/null +++ b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleMain.tsx @@ -0,0 +1,132 @@ +import { useBackend } from '../../backend'; +import { Button, LabeledList, Section } from '../../components'; +import { Data } from './types'; + +export const CommunicationsConsoleMain = (props) => { + const { act, data } = useBackend(); + + const { + messages, + msg_cooldown, + emagged, + cc_cooldown, + str_security_level, + levels, + authmax, + security_level, + security_level_color, + authenticated, + atcsquelch, + boss_short, + } = data; + + const reportText = 'View (' + messages.length + ')'; + let announceText = 'Make Priority Announcement'; + if (msg_cooldown > 0) { + announceText += ' (' + msg_cooldown + 's)'; + } + let ccMessageText = emagged ? 'Message [UNKNOWN]' : 'Message ' + boss_short; + if (cc_cooldown > 0) { + ccMessageText += ' (' + cc_cooldown + 's)'; + } + + const alertLevelText = str_security_level; + const alertLevelButtons = levels.map((slevel) => { + return ( + + ); + }); + + return ( + <> +
+ + + + + {(!!emagged && ( + + + + + )) || ( + + + + )} + +
+
+ + + {alertLevelText} + + + {alertLevelButtons} + + + + + + + + + + + +
+ + ); +}; diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleMessage.tsx b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleMessage.tsx new file mode 100644 index 0000000000..e70a61bae5 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleMessage.tsx @@ -0,0 +1,69 @@ +import { useBackend } from '../../backend'; +import { Box, Button, LabeledList, Section } from '../../components'; +import { Data } from './types'; + +export const CommunicationsConsoleMessage = (props) => { + const { act, data } = useBackend(); + + const { message_current, message_deletion_allowed, authenticated, messages } = + data; + + if (message_current) { + return ( +
act('messagelist')} + > + Return To Message List + + } + > + {message_current.contents} +
+ ); + } + + let messageRows = messages.map((m) => { + return ( + + + + + ); + }); + + return ( +
act('main')}> + Back To Main Menu + + } + > + + {(messages.length && messageRows) || ( + + No messages. + + )} + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleStatusDisplay.tsx b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleStatusDisplay.tsx new file mode 100644 index 0000000000..6ea5a7c319 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CommunicationsConsole/CommunicationsConsoleStatusDisplay.tsx @@ -0,0 +1,54 @@ +import { useBackend } from '../../backend'; +import { Button, LabeledList, Section } from '../../components'; +import { Data } from './types'; + +export const CommunicationsConsoleStatusDisplay = (props) => { + const { act, data } = useBackend(); + + const { stat_display, authenticated } = data; + + let presetButtons = stat_display['presets'].map((pb) => { + return ( + + ); + }); + return ( +
act('main')}> + Back To Main Menu + + } + > + + {presetButtons} + + + + + + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole/index.tsx b/tgui/packages/tgui/interfaces/CommunicationsConsole/index.tsx new file mode 100644 index 0000000000..891947d278 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CommunicationsConsole/index.tsx @@ -0,0 +1,12 @@ +import { Window } from '../../layouts'; +import { CommunicationsConsoleContent } from './CommunicationsConsoleContent'; + +export const CommunicationsConsole = (props) => { + return ( + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole/types.ts b/tgui/packages/tgui/interfaces/CommunicationsConsole/types.ts new file mode 100644 index 0000000000..3b3d1ac327 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CommunicationsConsole/types.ts @@ -0,0 +1,33 @@ +import { BooleanLike } from 'common/react'; + +export type Data = { + is_ai: BooleanLike; + menu_state: string; + emagged: BooleanLike; + authenticated: BooleanLike; + authmax: BooleanLike; + atcsquelch: BooleanLike; + boss_short: string; + stat_display: { + type: string; + line_1: string; + line_2: string; + presets: { name: string; label: string; desc: string }[]; + }; + security_level: number; + security_level_color: string; + str_security_level: string; + levels: { id: number; name: string; icon: string }[]; + messages: message[]; + message_deletion_allowed: BooleanLike; + message_current_id: number; + message_current: message; + current_viewing_message: number; + msg_cooldown: number; + cc_cooldown: number; + esc_callable: BooleanLike; + esc_recallable: BooleanLike; + esc_status: BooleanLike | string; +}; + +type message = { id: string; title: String; contents: string }; diff --git a/tgui/packages/tgui/interfaces/Communicator.tsx b/tgui/packages/tgui/interfaces/Communicator.tsx deleted file mode 100644 index 0bd2dd4d6b..0000000000 --- a/tgui/packages/tgui/interfaces/Communicator.tsx +++ /dev/null @@ -1,1307 +0,0 @@ -import { filter } from 'common/collections'; -import { BooleanLike } from 'common/react'; -import { decodeHtmlEntities, toTitleCase } from 'common/string'; -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'; - -const HOMETAB = 1; -const PHONTAB = 2; -const CONTTAB = 3; -const MESSTAB = 4; -const MESSSUBTAB = 40; -const NEWSTAB = 5; -const NOTETAB = 6; -const WTHRTAB = 7; -const MANITAB = 8; -const SETTTAB = 9; - -let TABS = [ - HOMETAB, - PHONTAB, - CONTTAB, - MESSTAB, - MESSSUBTAB, - NEWSTAB, - NOTETAB, - WTHRTAB, - MANITAB, - SETTTAB, -]; - -let TabToTemplate = {}; // Populated under each template - -type Data = { - // GENERAL - currentTab: number; - video_comm: BooleanLike; - mapRef: string; - - // FOOTER - time: string; - connectionStatus: BooleanLike; - owner: string; - occupation: string; - - // HEADER - flashlight: BooleanLike; - - // HOMETAB - homeScreen: { number: number; module: string; icon: string }[]; - - // PHONETAB - targetAddress: string; - voice_mobs: { name: string; true_name: string; ref: string }[]; - communicating: { - address: string; - name: string; - true_name: string; - ref: string; - }[]; - requestsReceived: { address: string; name: string; ref: string }[]; - invitesSent: { address: string; name: string }[]; - phone_video_comm: string; - selfie_mode: BooleanLike; - - // MESSAGING - imContacts: { address: string; name: string }[]; - targetAddressName: string; - imList: { address: string; to_address: string; im: string }[]; - - // SETTINGS - address: string; - visible: BooleanLike; - ring: BooleanLike; - - // NEWSTAB - feeds: { index: number; name: string }[]; - target_feed: { name: string; author: string; messages: NewsMessage[] }; - latest_news: NewsMessage[]; - - // NOTETAB - note: string; -}; - -type NewsMessage = { - ref: string; - body: string; - img: string; - caption: string; - message_type: string; - author: string; - time_stamp: string; - - index: number; - channel: string; -}; - -function notFound(val) { - return TABS.includes(val); -} - -export const Communicator = (props) => { - const { act, data } = useBackend(); - - const { - currentTab, - video_comm, - owner, - occupation, - connectionStatus, - address, - visible, - ring, - selfie_mode, - homeScreen, - targetAddress, - voice_mobs, - phone_video_comm, - communicating, - requestsReceived, - invitesSent, - imContacts, - targetAddressName, - imList, - feeds, - target_feed, - latest_news, - note, - } = data; - - const validCharacters = [ - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - 'A', - 'B', - 'C', - 'D', - 'E', - 'F', - ]; - - let buttonArray = validCharacters.map((char) => ( - - )); - - let finalArray: any[] = []; - - for (let i = 0; i < buttonArray.length; i += 4) { - finalArray.push( - - {buttonArray[i]} - {buttonArray[i + 1]} - {buttonArray[i + 2]} - {buttonArray[i + 3]} - , - ); - } - /* 0: Fullscreen Video - * 1: Popup Video - * 2: Minimized Video - */ - const [videoSetting, setVideoSetting] = useState(0); - const [clipboardMode, setClipboardMode] = useState(false); - - return ( - - - {video_comm && ( - - )} - {(!video_comm || videoSetting !== 0) && ( - <> - - - {(currentTab === HOMETAB && ( - - {homeScreen.map((app) => ( - - - {app.module} - - ))} - - )) || - (currentTab === PHONTAB && ( -
- - - - - - act('write_target_address', { val: val }) - } - /> - - - - Dial - - {/* Message */} - - - Message - - {/* Hang Up */} - - - Hang Up - - - - -
- - - - - -
- {(!!voice_mobs.length && ( - - {voice_mobs.map((mob) => ( - - - - ))} - - )) || No connections} -
-
- {(!!communicating.length && ( - - {communicating.map((comm) => ( - - - {decodeHtmlEntities(comm.name)} - - - - {(video_comm === null && ( - - )) || - (phone_video_comm === comm.ref && ( - - ))} - - - ))} -
- )) || No connections} -
-
- {(!!requestsReceived.length && ( - - {requestsReceived.map((request) => ( - - {decodeHtmlEntities(request.address)} - - - - - - ))} - - )) || No requests received.} -
-
- {(!!invitesSent.length && ( - - {invitesSent.map((invite) => ( - - {decodeHtmlEntities(invite.address)} - - - - - ))} - - )) || No invites sent.} -
-
-
- )) || - (currentTab === CONTTAB && ) || - (currentTab === MESSTAB && ( -
- {(imContacts.length && ( - - {imContacts.map((device) => ( - - - {decodeHtmlEntities(device.name)}: - - - {device.address} - - - - - - ))} -
- )) || ( - - You haven't sent any messages yet. - - - )} -
- )) || - (currentTab === MESSSUBTAB && - (clipboardMode ? ( -
- {enforceLengthLimit( - 'Conversation with ', - decodeHtmlEntities(targetAddressName), - 30, - )} - - } - buttons={ - -
- ) : ( -
- {enforceLengthLimit( - 'Conversation with ', - decodeHtmlEntities(targetAddressName), - 30, - )} - - } - buttons={ - -
- ))) || - (currentTab === NEWSTAB && ( -
- {(!feeds.length && ( - - Error: No newsfeeds available. Please try again later. - - )) || - (target_feed && ( -
- act('newsfeed', { newsfeed: null }) - } - > - Back - - } - > - {target_feed.messages.map((message) => ( -
- - {decodeHtmlEntities(message.body)} - {!!message.img && ( - - - {decodeHtmlEntities(message.caption) || null} - - )} - - [{message.message_type} by{' '} - {decodeHtmlEntities(message.author)} -{' '} - {message.time_stamp}] - -
- ))} -
- )) || ( - <> -
-
- {latest_news.map((news) => ( - -
- {decodeHtmlEntities(news.channel)} - -
- - {decodeHtmlEntities(news.body)} - {!!news.img && ( - - [image omitted, view story for more - details] - {news.caption || null} - - )} - - [{news.message_type} by{' '} - - {news.author} - {' '} - - {news.time_stamp}] - -
- ))} -
-
-
- {feeds.map((feed) => ( - - ))} -
- - )} -
- )) || - (currentTab === NOTETAB && ( -
act('edit')}> - Edit Notes - - } - > -
- {note} -
-
- )) || - (currentTab === WTHRTAB && ) || - (currentTab === MANITAB && ) || - (currentTab === SETTTAB && ( -
- - - - - - - - - {decodeHtmlEntities(occupation)} - - - {connectionStatus === 1 ? ( - Connected - ) : ( - Disconnected - )} - - - {address} - - - act('toggle_visibility')} - > - {visible - ? 'This device can be seen by other devices.' - : 'This device is invisible to other devices.'} - - - - act('toggle_ringer')} - > - {ring ? 'Ringer on.' : 'Ringer off.'} - - - - -
- )) || - (notFound(currentTab) && )} -
- - - )} -
-
- ); -}; - -const VideoComm = (props) => { - const { act, data } = useBackend(); - - const { video_comm, mapRef } = data; - - const { videoSetting, setVideoSetting } = props; - - if (videoSetting === 0) { - return ( - - - - -
- - - ); - } - return null; -}; - -const TemplateError = (props) => { - const { act, data } = useBackend(); - - const { currentTab } = data; - - return ( -
- You tried to access tab #{currentTab}, but there was no template defined! -
- ); -}; - -const CommunicatorHeader = (props) => { - const { act, data } = useBackend(); - - const { time, connectionStatus, owner, occupation } = data; - - return ( -
- - {time} - - - - {decodeHtmlEntities(owner)} - {decodeHtmlEntities(occupation)} - -
- ); -}; - -const CommunicatorFooter = (props) => { - const { act, data } = useBackend(); - - const { flashlight } = data; - - const { videoSetting, setVideoSetting } = props; - - return ( - - -
+ + + ); + } + return null; +}; diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorHomeTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorHomeTab.tsx new file mode 100644 index 0000000000..9e8809cc77 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorHomeTab.tsx @@ -0,0 +1,67 @@ +import { useBackend } from '../../backend'; +import { Box, Button, Flex, Icon } from '../../components'; +import { Data } from './types'; + +export const CommunicatorHomeTab = (props) => { + const { act, data } = useBackend(); + + const { homeScreen } = data; + + return ( + + {homeScreen.map((app) => ( + + + {app.module} + + ))} + + ); +}; + +/* Helper for notifications (yes this is a mess, but whatever, it works) */ +const hasNotifications = (app: string | null) => { + const { data } = useBackend(); + + const { + /* Phone Notifications */ + voice_mobs, + communicating, + requestsReceived, + invitesSent, + video_comm, + } = data; + + if (app === 'Phone') { + if ( + voice_mobs.length || + communicating.length || + requestsReceived.length || + invitesSent.length || + video_comm + ) { + return true; + } + } + + return false; +}; diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorMessageSubTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorMessageSubTab.tsx new file mode 100644 index 0000000000..5e15ccfb36 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorMessageSubTab.tsx @@ -0,0 +1,190 @@ +import { decodeHtmlEntities } from 'common/string'; + +import { useBackend } from '../../backend'; +import { Box, Button, Section } from '../../components'; +import { Data } from './types'; + +export const CommunicatorMessageSubTab = (props: { + clipboardMode: boolean; + onClipboardMode: Function; +}) => { + const { act, data } = useBackend(); + + const { clipboardMode, onClipboardMode } = props; + + const { targetAddress, targetAddressName, imList } = data; + + return clipboardMode ? ( +
+ {enforceLengthLimit( + 'Conversation with ', + decodeHtmlEntities(targetAddressName), + 30, + )} + + } + buttons={ + +
+ ) : ( +
+ {enforceLengthLimit( + 'Conversation with ', + decodeHtmlEntities(targetAddressName), + 30, + )} + + } + buttons={ + +
+ ); +}; + +/* Actual messaging conversation */ +const IsIMOurs = ( + im: { address: string; to_address: string; im: string }, + targetAddress: string, +) => { + return im.address !== targetAddress; +}; + +const enforceLengthLimit = (prefix: string, name: string, length: number) => { + if ((prefix + name).length > length) { + if (name.length > length) { + return name.slice(0, length) + '...'; + } + return name; + } + return prefix + name; +}; + +const findClassMessage = ( + im: { address: string; to_address: string; im: string }, + targetAddress: string, + lastIndex: number, + filterArray: { + address: string; + to_address: string; + im: string; + }[], +) => { + if (lastIndex < 0 || lastIndex > filterArray.length) { + return IsIMOurs(im, targetAddress) + ? 'TinderMessage_First_Sent' + : 'TinderMessage_First_Received'; + } + + let thisSent = IsIMOurs(im, targetAddress); + let lastSent = IsIMOurs(filterArray[lastIndex], targetAddress); + if (thisSent && lastSent) { + return 'TinderMessage_Subsequent_Sent'; + } else if (!thisSent && !lastSent) { + return 'TinderMessage_Subsequent_Received'; + } + return thisSent ? 'TinderMessage_First_Sent' : 'TinderMessage_First_Received'; +}; diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorMessageTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorMessageTab.tsx new file mode 100644 index 0000000000..6fe344719a --- /dev/null +++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorMessageTab.tsx @@ -0,0 +1,63 @@ +import { decodeHtmlEntities } from 'common/string'; + +import { useBackend } from '../../backend'; +import { Box, Button, Section, Table } from '../../components'; +import { CONTTAB, MESSSUBTAB } from './constants'; +import { Data } from './types'; + +export const CommunicatorMessageTab = (props) => { + const { act, data } = useBackend(); + + const { imContacts } = data; + + return ( +
+ {(imContacts.length && ( + + {imContacts.map((device) => ( + + + {decodeHtmlEntities(device.name)}: + + + {device.address} + + + + + + ))} +
+ )) || ( + + You haven't sent any messages yet. + + + )} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorNewsTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorNewsTab.tsx new file mode 100644 index 0000000000..5ed6049bfd --- /dev/null +++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorNewsTab.tsx @@ -0,0 +1,105 @@ +import { decodeHtmlEntities } from 'common/string'; + +import { useBackend } from '../../backend'; +import { Box, Button, Section } from '../../components'; +import { Data } from './types'; + +export const CommunicatorNewsTab = (props) => { + const { act, data } = useBackend(); + + const { feeds, target_feed, latest_news } = data; + + return ( +
+ {(!feeds.length && ( + + Error: No newsfeeds available. Please try again later. + + )) || + (target_feed && ( +
act('newsfeed', { newsfeed: null })} + > + Back + + } + > + {target_feed.messages.map((message) => ( +
+ - {decodeHtmlEntities(message.body)} + {!!message.img && ( + + + {decodeHtmlEntities(message.caption) || null} + + )} + + [{message.message_type} by{' '} + {decodeHtmlEntities(message.author)} - {message.time_stamp}] + +
+ ))} +
+ )) || ( + <> +
+
+ {latest_news.map((news) => ( + +
+ {decodeHtmlEntities(news.channel)} + +
+ - {decodeHtmlEntities(news.body)} + {!!news.img && ( + + [image omitted, view story for more details] + {news.caption || null} + + )} + + [{news.message_type} by{' '} + + {news.author} + {' '} + - {news.time_stamp}] + +
+ ))} +
+
+
+ {feeds.map((feed) => ( + + ))} +
+ + )} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorNoteTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorNoteTab.tsx new file mode 100644 index 0000000000..35edfc6a61 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorNoteTab.tsx @@ -0,0 +1,34 @@ +import { useBackend } from '../../backend'; +import { Button, Section } from '../../components'; +import { Data } from './types'; + +export const CommunicatorNoteTab = (props) => { + const { act, data } = useBackend(); + + const { note } = data; + + return ( +
act('edit')}> + Edit Notes + + } + > +
+ {note} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorPhoneTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorPhoneTab.tsx new file mode 100644 index 0000000000..4e50303131 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorPhoneTab.tsx @@ -0,0 +1,328 @@ +import { decodeHtmlEntities } from 'common/string'; + +import { useBackend } from '../../backend'; +import { + Box, + Button, + Flex, + Icon, + Input, + LabeledList, + Section, + Table, +} from '../../components'; +import { MESSTAB } from './constants'; +import { Data } from './types'; + +export const CommunicatorPhoneTab = (props) => { + const { act, data } = useBackend(); + + const { selfie_mode, targetAddress } = data; + + const validCharacters = [ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + ]; + + const buttonArray = validCharacters.map((char) => ( + + )); + + let finalArray: React.JSX.Element[] = []; + + for (let i = 0; i < buttonArray.length; i += 4) { + finalArray.push( + + {buttonArray[i]} + {buttonArray[i + 1]} + {buttonArray[i + 2]} + {buttonArray[i + 3]} + , + ); + } + + return ( +
+ + + + + act('write_target_address', { val: val })} + /> + + + + Dial + + {/* Message */} + + + Message + + {/* Hang Up */} + + + Hang Up + + + + +
+ + + + + + + + + +
+
+ ); +}; + +const CommunicatorPhoneTabExternal = (props) => { + const { act, data } = useBackend(); + + const { voice_mobs } = data; + + return ( +
+ {(!!voice_mobs.length && ( + + {voice_mobs.map((mob) => ( + + + + ))} + + )) || No connections} +
+ ); +}; + +const CommunicatorPhoneTabInternal = (props) => { + const { act, data } = useBackend(); + + const { communicating, video_comm, phone_video_comm } = data; + + return ( +
+ {(!!communicating.length && ( + + {communicating.map((comm) => ( + + + {decodeHtmlEntities(comm.name)} + + + + {(video_comm === null && ( + + )) || + (phone_video_comm === comm.ref && ( + + ))} + + + ))} +
+ )) || No connections} +
+ ); +}; + +const CommunicatorPhoneTabRequest = (props) => { + const { act, data } = useBackend(); + + const { requestsReceived } = data; + + return ( +
+ {(!!requestsReceived.length && ( + + {requestsReceived.map((request) => ( + + {decodeHtmlEntities(request.address)} + + + + + + ))} + + )) || No requests received.} +
+ ); +}; + +const CommunicatorPhoneTabInvite = (props) => { + const { act, data } = useBackend(); + + const { invitesSent } = data; + + return ( +
+ {(!!invitesSent.length && ( + + {invitesSent.map((invite) => ( + + {decodeHtmlEntities(invite.address)} + + + + + ))} + + )) || No invites sent.} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorSettingsTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorSettingsTab.tsx new file mode 100644 index 0000000000..3cfcb217b1 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorSettingsTab.tsx @@ -0,0 +1,74 @@ +import { decodeHtmlEntities } from 'common/string'; + +import { useBackend } from '../../backend'; +import { Box, Button, LabeledList, Section } from '../../components'; +import { Data } from './types'; + +export const CommunicatorSettingsTab = (props) => { + const { act, data } = useBackend(); + + const { + address, + selfie_mode, + owner, + occupation, + connectionStatus, + visible, + ring, + } = data; + + return ( +
+ + + + + + + + + {decodeHtmlEntities(occupation)} + + + {connectionStatus === 1 ? ( + Connected + ) : ( + Disconnected + )} + + + {address} + + + act('toggle_visibility')} + > + {visible + ? 'This device can be seen by other devices.' + : 'This device is invisible to other devices.'} + + + + act('toggle_ringer')} + > + {ring ? 'Ringer on.' : 'Ringer off.'} + + + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Communicator/CommunicatorWeatherTab.tsx b/tgui/packages/tgui/interfaces/Communicator/CommunicatorWeatherTab.tsx new file mode 100644 index 0000000000..5c7412253d --- /dev/null +++ b/tgui/packages/tgui/interfaces/Communicator/CommunicatorWeatherTab.tsx @@ -0,0 +1,97 @@ +import { filter } from 'common/collections'; +import { decodeHtmlEntities, toTitleCase } from 'common/string'; + +import { useBackend } from '../../backend'; +import { Box, LabeledList, Section } from '../../components'; +import { AirContent, WeatherTabData } from './types'; + +export const CommunicatorWeatherTab = (props) => { + const { act, data } = useBackend(); + + const { aircontents, weather } = data; + + const deg: string = '\u00B0'; + + return ( +
+
+ + {filter( + (i: AirContent) => + i.val !== '0' || + i.entry === 'Pressure' || + i.entry === 'Temperature', + )(aircontents).map((item: AirContent) => ( + + {item.val} + {decodeHtmlEntities(item.units)} + + ))} + +
+
+ {(!!weather.length && ( + + {weather.map((wr) => ( + + + {wr.Time} + + {toTitleCase(wr.Weather)} + + + Current: {wr.Temperature.toFixed()} {deg}C | High:{' '} + {wr.High.toFixed()} {deg}C | Low: {wr.Low.toFixed()} {deg}C + + + {wr.WindDir} + + + {wr.WindSpeed} + + + {decodeHtmlEntities(wr.Forecast)} + + + + ))} + + )) || ( + + No weather reports available. Please check back later. + + )} +
+
+ ); +}; + +/* Weather App */ +const getItemColor = ( + value: number, + min2: number, + min1: number, + max1: number, + max2: number, +) => { + if (value < min2) { + return 'bad'; + } else if (value < min1) { + return 'average'; + } else if (value > max1) { + return 'average'; + } else if (value > max2) { + return 'bad'; + } + return 'good'; +}; diff --git a/tgui/packages/tgui/interfaces/Communicator/constants.ts b/tgui/packages/tgui/interfaces/Communicator/constants.ts new file mode 100644 index 0000000000..f37d974320 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Communicator/constants.ts @@ -0,0 +1,27 @@ +export const HOMETAB = 1; +export const PHONTAB = 2; +export const CONTTAB = 3; +export const MESSTAB = 4; +export const MESSSUBTAB = 40; +export const NEWSTAB = 5; +export const NOTETAB = 6; +export const WTHRTAB = 7; +export const MANITAB = 8; +export const SETTTAB = 9; + +export const tabs = [ + HOMETAB, + PHONTAB, + CONTTAB, + MESSTAB, + MESSSUBTAB, + NEWSTAB, + NOTETAB, + WTHRTAB, + MANITAB, + SETTTAB, +]; + +export function notFound(val) { + return tabs.includes(val); +} diff --git a/tgui/packages/tgui/interfaces/Communicator/index.tsx b/tgui/packages/tgui/interfaces/Communicator/index.tsx new file mode 100644 index 0000000000..0194169071 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Communicator/index.tsx @@ -0,0 +1,88 @@ +import { useState } from 'react'; + +import { useBackend } from '../../backend'; +import { Box } from '../../components'; +import { Window } from '../../layouts'; +import { CrewManifestContent } from '../CrewManifest'; +import { CommunicatorContactTab } from './CommunicatorContactTab'; +import { + CommunicatorFooter, + CommunicatorHeader, + TemplateError, + VideoComm, +} from './CommunicatorGeneral'; +import { CommunicatorHomeTab } from './CommunicatorHomeTab'; +import { CommunicatorMessageSubTab } from './CommunicatorMessageSubTab'; +import { CommunicatorMessageTab } from './CommunicatorMessageTab'; +import { CommunicatorNewsTab } from './CommunicatorNewsTab'; +import { CommunicatorNoteTab } from './CommunicatorNoteTab'; +import { CommunicatorPhoneTab } from './CommunicatorPhoneTab'; +import { CommunicatorSettingsTab } from './CommunicatorSettingsTab'; +import { CommunicatorWeatherTab } from './CommunicatorWeatherTab'; +import { notFound, tabs } from './constants'; +import { Data } from './types'; + +export const Communicator = () => { + const { act, data } = useBackend(); + + const { currentTab, video_comm } = data; + + const tab: React.JSX.Element[] = []; + + const [videoSetting, setVideoSetting] = useState(0); + const [clipboardMode, setClipboardMode] = useState(false); + + function handleClipboardMode(value: boolean) { + setClipboardMode(value); + } + + tab[tabs[0]] = ; + tab[tabs[1]] = ; + tab[tabs[2]] = ; + tab[tabs[3]] = ; + tab[tabs[4]] = ( + + ); + tab[tabs[5]] = ; + tab[tabs[6]] = ; + tab[tabs[7]] = ; + tab[tabs[8]] = ; + tab[tabs[9]] = ; + + return ( + + + {video_comm && ( + + )} + {(!video_comm || videoSetting !== 0) && ( + <> + + + {tab[currentTab] || + (notFound(currentTab) && ( + + ))} + + + + )} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/Communicator/types.ts b/tgui/packages/tgui/interfaces/Communicator/types.ts new file mode 100644 index 0000000000..7d32b0e34b --- /dev/null +++ b/tgui/packages/tgui/interfaces/Communicator/types.ts @@ -0,0 +1,96 @@ +import { BooleanLike } from 'common/react'; + +export type Data = { + // GENERAL + currentTab: number; + video_comm: BooleanLike; + mapRef: string; + + // FOOTER + time: string; + connectionStatus: BooleanLike; + owner: string; + occupation: string; + + // HEADER + flashlight: BooleanLike; + + // HOMETAB + homeScreen: { number: number; module: string; icon: string }[]; + + // PHONETAB + targetAddress: string; + voice_mobs: { name: string; true_name: string; ref: string }[]; + communicating: { + address: string; + name: string; + true_name: string; + ref: string; + }[]; + requestsReceived: { address: string; name: string; ref: string }[]; + invitesSent: { address: string; name: string }[]; + phone_video_comm: string; + selfie_mode: BooleanLike; + + // MESSAGING + imContacts: { address: string; name: string }[]; + targetAddressName: string; + imList: { address: string; to_address: string; im: string }[]; + + // SETTINGS + address: string; + visible: BooleanLike; + ring: BooleanLike; + + // NEWSTAB + feeds: { index: number; name: string }[]; + target_feed: { name: string; author: string; messages: NewsMessage[] } | null; + latest_news: NewsMessage[]; + + // NOTETAB + note: string; +}; + +type NewsMessage = { + ref: string; + body: string; + img: string; + caption: string; + message_type: string; + author: string; + time_stamp: string; + + index: number; + channel: string; +}; + +export type ContactsTabData = { + knownDevices: { address: string; name: string }[]; +}; + +export type WeatherTabData = { + aircontents: AirContent[]; + weather: Weather[]; +}; + +export type AirContent = { + entry: string; + val; + bad_low: number; + poor_low: number; + poor_high: number; + bad_high: number; + units; +}; + +type Weather = { + Planet: string; + Time: string; + Weather: string; + Temperature; + High; + Low; + WindDir; + WindSpeed; + Forecast: string; +}; diff --git a/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep1.tsx b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep1.tsx new file mode 100644 index 0000000000..e950bdad7c --- /dev/null +++ b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep1.tsx @@ -0,0 +1,53 @@ +import { useBackend } from '../../backend'; +import { Box, Button, Section, Table } from '../../components'; + +// This had a pretty gross backend so this was unfortunately one of the +// best ways of doing it. +export const CfStep1 = (props) => { + const { act } = useBackend(); + return ( +
+ + Choose your Device + + + + + + + + + + + +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ComputerFabricator.jsx b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep2.tsx similarity index 67% rename from tgui/packages/tgui/interfaces/ComputerFabricator.jsx rename to tgui/packages/tgui/interfaces/ComputerFabricator/CfStep2.tsx index 619f5c0feb..6975129674 100644 --- a/tgui/packages/tgui/interfaces/ComputerFabricator.jsx +++ b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep2.tsx @@ -1,91 +1,31 @@ import { multiline } from 'common/string'; -import { useBackend } from '../backend'; -import { Box, Button, Section, Table, Tooltip } from '../components'; -import { Window } from '../layouts'; +import { useBackend } from '../../backend'; +import { Box, Button, Section, Table, Tooltip } from '../../components'; +import { Data } from './types'; -export const ComputerFabricator = (props) => { - const { act, data } = useBackend(); - return ( - - -
- Your perfect device, only three steps away... -
- {data.state !== 0 && ( - - )} - {data.state === 0 && } - {data.state === 1 && } - {data.state === 2 && } - {data.state === 3 && } -
-
- ); -}; +export const CfStep2 = (props) => { + const { act, data } = useBackend(); -// This had a pretty gross backend so this was unfortunately one of the -// best ways of doing it. -const CfStep1 = (props) => { - const { act, data } = useBackend(); - return ( -
- - Choose your Device - - - - - - - - - - - -
-
-
- ); -}; + const { + totalprice, + hw_battery, + hw_disk, + hw_netcard, + hw_nanoprint, + hw_card, + devtype, + hw_cpu, + hw_tesla, + } = data; -const CfStep2 = (props) => { - const { act, data } = useBackend(); return (
- {data.totalprice}â‚® + {totalprice}â‚® } > @@ -103,7 +43,7 @@ const CfStep2 = (props) => { - {data.devtype !== 2 && ( + {devtype !== 2 && ( Processor Unit: @@ -329,7 +269,7 @@ const CfStep2 = (props) => {
); }; - -const CfStep3 = (props) => { - const { act, data } = useBackend(); - return ( -
- - Your device is ready for fabrication... - - - Please swipe your ID now to authorize payment of: -   - - {data.totalprice}â‚® - - -
- ); -}; - -const CfStep4 = (props) => { - return ( -
- - Thank you for your purchase! - - - If you experience any difficulties with your new device, please contact - your local network administrator. - -
- ); -}; diff --git a/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep3.tsx b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep3.tsx new file mode 100644 index 0000000000..668b739f29 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep3.tsx @@ -0,0 +1,19 @@ +import { Box, Section } from '../../components'; + +export const CfStep3 = (props: { totalprice: number }) => { + const { totalprice } = props; + return ( +
+ + Your device is ready for fabrication... + + + Please swipe your ID now to authorize payment of: +   + + {totalprice}â‚® + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep4.tsx b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep4.tsx new file mode 100644 index 0000000000..62fb59fa85 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ComputerFabricator/CfStep4.tsx @@ -0,0 +1,15 @@ +import { Box, Section } from '../../components'; + +export const CfStep4 = (props) => { + return ( +
+ + Thank you for your purchase! + + + If you experience any difficulties with your new device, please contact + your local network administrator. + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ComputerFabricator/index.tsx b/tgui/packages/tgui/interfaces/ComputerFabricator/index.tsx new file mode 100644 index 0000000000..b3919e0d0a --- /dev/null +++ b/tgui/packages/tgui/interfaces/ComputerFabricator/index.tsx @@ -0,0 +1,37 @@ +import { useBackend } from '../../backend'; +import { Button, Section } from '../../components'; +import { Window } from '../../layouts'; +import { CfStep1 } from './CfStep1'; +import { CfStep2 } from './CfStep2'; +import { CfStep3 } from './CfStep3'; +import { CfStep4 } from './CfStep4'; +import { Data } from './types'; + +export const ComputerFabricator = (props) => { + const { act, data } = useBackend(); + + const { state, totalprice } = data; + + const tab: React.JSX.Element[] = []; + + tab[0] = ; + tab[1] = ; + tab[2] = ; + tab[3] = ; + + return ( + + +
+ Your perfect device, only three steps away... +
+ {state !== 0 && ( + + )} + {tab[state]} +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/ComputerFabricator/types.ts b/tgui/packages/tgui/interfaces/ComputerFabricator/types.ts new file mode 100644 index 0000000000..405a1007bd --- /dev/null +++ b/tgui/packages/tgui/interfaces/ComputerFabricator/types.ts @@ -0,0 +1,14 @@ +import { BooleanLike } from 'common/react'; + +export type Data = { + state: number; + devtype: number | undefined; + hw_battery: number | undefined; + hw_disk: number | undefined; + hw_netcard: number | undefined; + hw_tesla: BooleanLike; + hw_nanoprint: BooleanLike; + hw_card: BooleanLike; + hw_cpu: number | undefined; + totalprice: number | undefined; +}; diff --git a/tgui/packages/tgui/interfaces/CookingAppliance.jsx b/tgui/packages/tgui/interfaces/CookingAppliance.tsx similarity index 87% rename from tgui/packages/tgui/interfaces/CookingAppliance.jsx rename to tgui/packages/tgui/interfaces/CookingAppliance.tsx index 4efa443a6a..7b951cd5db 100644 --- a/tgui/packages/tgui/interfaces/CookingAppliance.jsx +++ b/tgui/packages/tgui/interfaces/CookingAppliance.tsx @@ -1,3 +1,5 @@ +import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; import { AnimatedNumber, @@ -9,8 +11,22 @@ import { } from '../components'; import { Window } from '../layouts'; +type Data = { + temperature: number; + optimalTemp: number; + temperatureEnough: BooleanLike; + efficiency: number; + containersRemovable: BooleanLike; + our_contents: { + empty: BooleanLike; + progress: number; + progressText: string; + container: string | null; + }[]; +}; + export const CookingAppliance = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { temperature, diff --git a/tgui/packages/tgui/interfaces/CrewMonitor.jsx b/tgui/packages/tgui/interfaces/CrewMonitor.jsx deleted file mode 100644 index 0dfb9c3955..0000000000 --- a/tgui/packages/tgui/interfaces/CrewMonitor.jsx +++ /dev/null @@ -1,188 +0,0 @@ -import { sortBy } from 'common/collections'; -import { flow } from 'common/fp'; -import { useState } from 'react'; - -import { useBackend } from '../backend'; -import { Box, Button, Icon, NanoMap, Table, Tabs } from '../components'; -import { Window } from '../layouts'; - -const getStatText = (cm) => { - if (cm.dead) { - return 'Deceased'; - } - if (parseInt(cm.stat, 10) === 1) { - // Unconscious - return 'Unconscious'; - } - return 'Living'; -}; - -const getStatColor = (cm) => { - if (cm.dead) { - return 'red'; - } - if (parseInt(cm.stat, 10) === 1) { - // Unconscious - return 'orange'; - } - return 'green'; -}; - -export const CrewMonitor = () => { - const [tabIndex, setTabIndex] = useState(0); - const [zoom, setZoom] = useState(1); - - function handleTabIndex(value) { - setTabIndex(value); - } - - function handleZoom(value) { - setZoom(value); - } - - return ( - - - - - - ); -}; - -export const CrewMonitorContent = (props) => { - const { act, data, config } = useBackend(); - - const crew = flow([ - sortBy((cm) => cm.name), - sortBy((cm) => cm?.x), - sortBy((cm) => cm?.y), - sortBy((cm) => cm?.realZ), - ])(data.crewmembers || []); - - let body; - // Data view - if (props.tabIndex === 0) { - body = ( - - - Name - Status - Location - - {crew.map((cm) => ( - - - {cm.name} ({cm.assignment}) - - - - {getStatText(cm)} - - {cm.sensor_type >= 2 ? ( - - {'('} - - {cm.brute} - - {'|'} - - {cm.fire} - - {'|'} - - {cm.tox} - - {'|'} - - {cm.oxy} - - {')'} - - ) : null} - - - {cm.sensor_type === 3 ? ( - data.isAI ? ( - - ) : ( - cm.area + ' (' + cm.x + ', ' + cm.y + ', ' + cm.z + ')' - ) - ) : ( - 'Not Available' - )} - - - ))} -
- ); - } else if (props.tabIndex === 1) { - // Please note, if you ever change the zoom values, - // you MUST update styles/components/Tooltip.scss - // and change the @for scss to match. - body = ; - } else { - body = 'ERROR'; - } - - return ( - <> - - props.onTabIndex(0)} - > - Data View - - props.onTabIndex(1)} - > - Map View - - - {body} - - ); -}; - -const CrewMonitorMapView = (props) => { - const { act, config, data } = useBackend(); - return ( - - props.onZoom(v)}> - {data.crewmembers - .filter( - (x) => x.sensor_type === 3 && ~~x.realZ === ~~config.mapZLevel, - ) - .map((cm) => ( - - ))} - - - ); -}; diff --git a/tgui/packages/tgui/interfaces/CrewMonitor.tsx b/tgui/packages/tgui/interfaces/CrewMonitor.tsx new file mode 100644 index 0000000000..385f158dd8 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CrewMonitor.tsx @@ -0,0 +1,231 @@ +import { sortBy } from 'common/collections'; +import { flow } from 'common/fp'; +import { BooleanLike } from 'common/react'; +import { useState } from 'react'; + +import { useBackend } from '../backend'; +import { Box, Button, Icon, NanoMap, Table, Tabs } from '../components'; +import { Window } from '../layouts'; + +type Data = { + zoomScale: number; + isAI: BooleanLike; + map_levels: number[]; + crewmembers: crewmember[]; +}; + +type crewmember = { + sensor_type: number; + name: string; + rank: string; + assignment: string; + dead: BooleanLike; + stat: number; + oxy: number; + tox: number; + fire: number; + brute: number; + area: string; + x: number; + y: number; + realZ: number; + z: number; + ref: string; +}; + +const getStatText = (cm: crewmember) => { + if (cm.dead) { + return 'Deceased'; + } + if (cm.stat === 1) { + // Unconscious + return 'Unconscious'; + } + return 'Living'; +}; + +const getStatColor = (cm: crewmember) => { + if (cm.dead) { + return 'red'; + } + if (cm.stat === 1) { + // Unconscious + return 'orange'; + } + return 'green'; +}; + +export const CrewMonitor = () => { + const [tabIndex, setTabIndex] = useState(0); + const [zoom, setZoom] = useState(1); + + function handleTabIndex(value: number) { + setTabIndex(value); + } + + function handleZoom(value: number) { + setZoom(value); + } + + return ( + + + + + + ); +}; + +export const CrewMonitorContent = (props: { + tabIndex: number; + zoom: number; + onTabIndex: Function; + onZoom: Function; +}) => { + const { data } = useBackend(); + + const { crewmembers = [] } = data; + + const crew: crewmember[] = flow([ + sortBy((cm: crewmember) => cm.name), + sortBy((cm: crewmember) => cm?.x), + sortBy((cm: crewmember) => cm?.y), + sortBy((cm: crewmember) => cm?.realZ), + ])(crewmembers); + + const tab: React.JSX.Element[] = []; + // Data view + // Please note, if you ever change the zoom values, + // you MUST update styles/components/Tooltip.scss + // and change the @for scss to match. + tab[0] = ; + + tab[1] = ; + + return ( + <> + + props.onTabIndex(0)} + > + Data View + + props.onTabIndex(1)} + > + Map View + + + {tab[props.tabIndex] || ERROR} + + ); +}; + +const CrewMonitorCrew = (props: { crew: crewmember[] }) => { + const { act, data } = useBackend(); + + const { crew } = props; + + const { isAI } = data; + + return ( + + + Name + Status + Location + + {crew.map((cm) => ( + + + {cm.name} ({cm.assignment}) + + + + {getStatText(cm)} + + {cm.sensor_type >= 2 ? ( + + {'('} + + {cm.brute} + + {'|'} + + {cm.fire} + + {'|'} + + {cm.tox} + + {'|'} + + {cm.oxy} + + {')'} + + ) : null} + + + {cm.sensor_type === 3 ? ( + isAI ? ( + + ) : ( + cm.area + ' (' + cm.x + ', ' + cm.y + ', ' + cm.z + ')' + ) + ) : ( + 'Not Available' + )} + + + ))} +
+ ); +}; + +const CrewMonitorMapView = (props: { zoom: number; onZoom: Function }) => { + const { config, data } = useBackend(); + + const { zoomScale, crewmembers } = data; + + return ( + + props.onZoom(v)}> + {crewmembers + .filter( + (x) => x.sensor_type === 3 && ~~x.realZ === ~~config.mapZLevel, + ) + .map((cm) => ( + + ))} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/Cryo.jsx b/tgui/packages/tgui/interfaces/Cryo/CryoContent.tsx similarity index 78% rename from tgui/packages/tgui/interfaces/Cryo.jsx rename to tgui/packages/tgui/interfaces/Cryo/CryoContent.tsx index 613b1905a5..994f881170 100644 --- a/tgui/packages/tgui/interfaces/Cryo.jsx +++ b/tgui/packages/tgui/interfaces/Cryo/CryoContent.tsx @@ -1,6 +1,6 @@ import { toFixed } from 'common/math'; -import { useBackend } from '../backend'; +import { useBackend } from '../../backend'; import { AnimatedNumber, Box, @@ -10,50 +10,15 @@ import { LabeledList, ProgressBar, Section, -} from '../components'; -import { Window } from '../layouts'; +} from '../../components'; +import { Data } from './types'; -const damageTypes = [ - { - label: 'Resp.', - type: 'oxyLoss', - }, - { - label: 'Toxin', - type: 'toxLoss', - }, - { - label: 'Brute', - type: 'bruteLoss', - }, - { - label: 'Burn', - type: 'fireLoss', - }, -]; - -const statNames = [ - ['good', 'Conscious'], - ['average', 'Unconscious'], - ['bad', 'DEAD'], -]; - -export const Cryo = (props) => { - return ( - - - - - - ); -}; - -const CryoContent = (props) => { - const { act, data } = useBackend(); +export const CryoContent = (props) => { + const { act, data } = useBackend(); const { isOperating, hasOccupant, - occupant = [], + occupant, cellTemperature, cellTemperatureStatus, isBeakerLoaded, @@ -62,7 +27,7 @@ const CryoContent = (props) => { <>
{ 0 ? 'good' : 'average'} > @@ -104,8 +69,8 @@ const CryoContent = (props) => { /> - {damageTypes.map((damageType) => ( - + {damageTypes.map((damageType, i) => ( + { ) : ( - +
No occupant detected.
@@ -163,7 +128,7 @@ const CryoContent = (props) => { }; const CryoBeaker = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { isBeakerLoaded, beakerLabel, beakerVolume } = data; if (isBeakerLoaded) { return ( diff --git a/tgui/packages/tgui/interfaces/Cryo/constants.ts b/tgui/packages/tgui/interfaces/Cryo/constants.ts new file mode 100644 index 0000000000..4bb990c953 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Cryo/constants.ts @@ -0,0 +1,24 @@ +const damageTypes: { label: string; type: string }[] = [ + { + label: 'Resp.', + type: 'oxyLoss', + }, + { + label: 'Toxin', + type: 'toxLoss', + }, + { + label: 'Brute', + type: 'bruteLoss', + }, + { + label: 'Burn', + type: 'fireLoss', + }, +]; + +const statNames: string[][] = [ + ['good', 'Conscious'], + ['average', 'Unconscious'], + ['bad', 'DEAD'], +]; diff --git a/tgui/packages/tgui/interfaces/Cryo/index.tsx b/tgui/packages/tgui/interfaces/Cryo/index.tsx new file mode 100644 index 0000000000..481740ce71 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Cryo/index.tsx @@ -0,0 +1,12 @@ +import { Window } from '../../layouts'; +import { CryoContent } from './CryoContent'; + +export const Cryo = (props) => { + return ( + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/Cryo/types.ts b/tgui/packages/tgui/interfaces/Cryo/types.ts new file mode 100644 index 0000000000..49d2d2e303 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Cryo/types.ts @@ -0,0 +1,23 @@ +import { BooleanLike } from 'common/react'; + +export type Data = { + isOperating: BooleanLike; + hasOccupant: BooleanLike; + occupant: { + name: string; + stat: number; + health: number; + maxHealth: number; + minHealth: number; + bruteLoss: number; + oxyLoss: number; + toxLoss: number; + fireLoss: number; + bodyTemperature: number; + }; + cellTemperature: number; + cellTemperatureStatus: string; + isBeakerLoaded: BooleanLike; + beakerLabel: string | null; + beakerVolume: number; +}; diff --git a/tgui/packages/tgui/interfaces/CryoStorage.jsx b/tgui/packages/tgui/interfaces/CryoStorage.tsx similarity index 61% rename from tgui/packages/tgui/interfaces/CryoStorage.jsx rename to tgui/packages/tgui/interfaces/CryoStorage.tsx index d947810e38..b564e95578 100644 --- a/tgui/packages/tgui/interfaces/CryoStorage.jsx +++ b/tgui/packages/tgui/interfaces/CryoStorage.tsx @@ -1,16 +1,29 @@ +import { BooleanLike } from 'common/react'; import { useState } from 'react'; import { useBackend } from '../backend'; -import { Box, Button, NoticeBox, Section, Tabs } from '../components'; +import { Box, NoticeBox, Section, Tabs } from '../components'; import { Window } from '../layouts'; +export type Data = { + real_name: string; + allow_items: BooleanLike; + crew: string[]; + items: string[]; +}; + export const CryoStorage = (props) => { - const { act, data } = useBackend(); + const { data } = useBackend(); const { real_name, allow_items } = data; const [tab, setTab] = useState(0); + const tabs: React.JSX.Element[] = []; + + tabs[0] = ; + tabs[1] = allow_items ? : ; + return ( @@ -25,15 +38,14 @@ export const CryoStorage = (props) => { )} Welcome, {real_name}. - {tab === 0 && } - {!!allow_items && tab === 1 && } + {tabs[tab]} ); }; export const CryoStorageCrew = (props) => { - const { act, data } = useBackend(); + const { data } = useBackend(); const { crew } = data; @@ -50,7 +62,29 @@ export const CryoStorageCrew = (props) => { }; export const CryoStorageItems = (props) => { - const { act, data } = useBackend(); + const { data } = useBackend(); + + const { items } = data; + + return ( +
+ {(items.length && + items.map((item) => ( + + {item} + + ))) || No items stored.} +
+ ); +}; + +export const CryoStorageDefaultError = (props) => { + return Disabled; +}; + +/* Unused here +export const CryoStorageItems = (props) => { + const { act, data } = useBackend(); const { items } = data; @@ -76,3 +110,4 @@ export const CryoStorageItems = (props) => {
); }; +*/ diff --git a/tgui/packages/tgui/interfaces/CryoStorageVr.jsx b/tgui/packages/tgui/interfaces/CryoStorageVr.jsx deleted file mode 100644 index 173842a7a8..0000000000 --- a/tgui/packages/tgui/interfaces/CryoStorageVr.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useState } from 'react'; - -import { useBackend } from '../backend'; -import { Box, NoticeBox, Section, Tabs } from '../components'; -import { Window } from '../layouts'; -import { CryoStorageCrew } from './CryoStorage'; - -export const CryoStorageVr = (props) => { - const { act, data } = useBackend(); - - const { real_name, allow_items } = data; - - const [tab, setTab] = useState(0); - - return ( - - - - setTab(0)}> - Crew - - {!!allow_items && ( - setTab(1)}> - Items - - )} - - Welcome, {real_name}. - {tab === 0 && } - {!!allow_items && tab === 1 && } - - - ); -}; - -export const CryoStorageItemsVr = (props) => { - const { act, data } = useBackend(); - - const { items } = data; - - return ( -
- {(items.length && - items.map((item) => ( - - {item} - - ))) || No items stored.} -
- ); -}; diff --git a/tgui/packages/tgui/interfaces/DNAForensics.jsx b/tgui/packages/tgui/interfaces/DNAForensics.tsx similarity index 89% rename from tgui/packages/tgui/interfaces/DNAForensics.jsx rename to tgui/packages/tgui/interfaces/DNAForensics.tsx index b1dc785183..ab160dc8ef 100644 --- a/tgui/packages/tgui/interfaces/DNAForensics.jsx +++ b/tgui/packages/tgui/interfaces/DNAForensics.tsx @@ -1,9 +1,18 @@ +import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; import { Box, Button, LabeledList, ProgressBar, Section } from '../components'; import { Window } from '../layouts'; +type Data = { + scan_progress: number; + scanning: BooleanLike; + bloodsamp: string; + bloodsamp_desc: string; +}; + export const DNAForensics = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { scan_progress, scanning, bloodsamp, bloodsamp_desc } = data; return ( diff --git a/tgui/packages/tgui/interfaces/DNAModifier.jsx b/tgui/packages/tgui/interfaces/DNAModifier.jsx deleted file mode 100644 index 72560e1470..0000000000 --- a/tgui/packages/tgui/interfaces/DNAModifier.jsx +++ /dev/null @@ -1,701 +0,0 @@ -import { useBackend } from '../backend'; -import { - Box, - Button, - Dimmer, - Flex, - Icon, - Knob, - LabeledList, - ProgressBar, - Section, - Tabs, -} from '../components'; -import { Window } from '../layouts'; -import { ComplexModal } from './common/ComplexModal'; - -const stats = [ - ['good', 'Alive'], - ['average', 'Unconscious'], - ['bad', 'DEAD'], -]; - -const operations = [ - ['ui', 'Modify U.I.', 'dna'], - ['se', 'Modify S.E.', 'dna'], - ['buffer', 'Transfer Buffers', 'syringe'], - ['rejuvenators', 'Rejuvenators', 'flask'], -]; - -const rejuvenatorsDoses = [5, 10, 20, 30, 50]; - -export const DNAModifier = (props) => { - const { act, data } = useBackend(); - const { irradiating, dnaBlockSize, occupant } = data; - const isDNAInvalid = - !occupant.isViableSubject || - !occupant.uniqueIdentity || - !occupant.structuralEnzymes; - let radiatingModal; - if (irradiating) { - radiatingModal = ; - } - return ( - - - {radiatingModal} - - - - - - ); -}; - -const DNAModifierOccupant = (props) => { - const { act, data } = useBackend(); - const { locked, hasOccupant, occupant } = data; - return ( -
- - Door Lock: - - - - - } - > - {hasOccupant ? ( - <> - - - {occupant.name} - - - - - {stats[occupant.stat][1]} - - - - - {props.isDNAInvalid ? ( - - -   The occupant's DNA structure is ruined beyond - recognition, please insert a subject with an intact DNA structure. - - ) : ( - - - - - - {data.occupant.uniqueEnzymes ? ( - data.occupant.uniqueEnzymes - ) : ( - - -   Unknown - - )} - - - )} - - ) : ( - Cell unoccupied. - )} -
- ); -}; - -const DNAModifierMain = (props) => { - const { act, data } = useBackend(); - const { selectedMenuKey, hasOccupant, occupant } = data; - if (!hasOccupant) { - return ( -
- - - -
- No occupant in DNA modifier. -
-
-
- ); - } else if (props.isDNAInvalid) { - return ( -
- - - -
- No operation possible on this subject. -
-
-
- ); - } - let body; - if (selectedMenuKey === 'ui') { - body = ( - <> - - - - ); - } else if (selectedMenuKey === 'se') { - body = ( - <> - - - - ); - } else if (selectedMenuKey === 'buffer') { - body = ; - } else if (selectedMenuKey === 'rejuvenators') { - body = ; - } - return ( -
- - {operations.map((op, i) => ( - act('selectMenuKey', { key: op[0] })} - > - - {op[1]} - - ))} - - {body} -
- ); -}; - -const DNAModifierMainUI = (props) => { - const { act, data } = useBackend(); - const { - selectedUIBlock, - selectedUISubBlock, - selectedUITarget, - dnaBlockSize, - occupant, - } = data; - return ( -
- - - - value.toString(16).toUpperCase()} - ml="0" - onChange={(e, val) => act('changeUITarget', { value: val })} - /> - - - -
- ); -}; - -const DNAModifierMainSE = (props) => { - const { act, data } = useBackend(); - const { selectedSEBlock, selectedSESubBlock, dnaBlockSize, occupant } = data; - return ( -
- - -
- ); -}; - -const DNAModifierMainRadiationEmitter = (props) => { - const { act, data } = useBackend(); - const { radiationIntensity, radiationDuration } = data; - return ( -
- - - act('radiationIntensity', { value: val })} - /> - - - act('radiationDuration', { value: val })} - /> - - - -
- ); -}; - -const DNAModifierMainBuffers = (props) => { - const { act, data } = useBackend(); - const { buffers } = data; - let bufferElements = buffers.map((buffer, i) => ( - - )); - return ( - <> -
- {bufferElements} -
- - - ); -}; - -const DNAModifierMainBuffersElement = (props) => { - const { act, data } = useBackend(); - const { id, name, buffer } = props; - const isInjectorReady = data.isInjectorReady; - const realName = name + (buffer.data ? ' - ' + buffer.label : ''); - return ( - -
- - act('bufferOption', { - option: 'clear', - id: id, - }) - } - > - Clear - - - - - } - > - - - - - - - - {!!buffer.data && ( - <> - - {buffer.owner || Unknown} - - - {buffer.type === 'ui' - ? 'Unique Identifiers' - : 'Structural Enzymes'} - {!!buffer.ue && ' and Unique Enzymes'} - - - - - - - - )} - - {!buffer.data && ( - - This buffer is empty. - - )} -
-
- ); -}; - -const DNAModifierMainBuffersDisk = (props) => { - const { act, data } = useBackend(); - const { hasDisk, disk } = data; - return ( -
- act('wipeDisk')} - > - Wipe - - - - } - > - {hasDisk ? ( - disk.data ? ( - - - {disk.label ? disk.label : 'No label'} - - - {disk.owner ? disk.owner : Unknown} - - - {disk.type === 'ui' ? 'Unique Identifiers' : 'Structural Enzymes'} - {!!disk.ue && ' and Unique Enzymes'} - - - ) : ( - Disk is blank. - ) - ) : ( - - -
- No disk inserted. -
- )} -
- ); -}; - -const DNAModifierMainRejuvenators = (props) => { - const { act, data } = useBackend(); - const { isBeakerLoaded, beakerVolume, beakerLabel } = data; - return ( -
act('ejectBeaker')} - > - Eject - - } - > - {isBeakerLoaded ? ( - - - {rejuvenatorsDoses.map((a, i) => ( - - ))} - - - - {beakerLabel ? beakerLabel : 'No label'} - {beakerVolume ? ( - - {beakerVolume} unit{beakerVolume === 1 ? '' : 's'} remaining - - ) : ( - Empty - )} - - - ) : ( - - -
- No beaker loaded. -
- )} -
- ); -}; - -const DNAModifierIrradiating = (props) => { - return ( - - -
- -

- -  Irradiating occupant  - -

-
- -

- For {props.duration} second{props.duration === 1 ? '' : 's'} -

-
-
- ); -}; - -const DNAModifierBlocks = (props) => { - const { act, data } = useBackend(); - const { dnaString, selectedBlock, selectedSubblock, blockSize, action } = - props; - - const characters = dnaString.split(''); - let curBlock = 0; - let dnaBlocks = []; - for (let block = 0; block < characters.length; block += blockSize) { - const realBlock = block / blockSize + 1; - let subBlocks = []; - for (let subblock = 0; subblock < blockSize; subblock++) { - const realSubblock = subblock + 1; - subBlocks.push( - , - ); - } - dnaBlocks.push( - - - {realBlock} - - {subBlocks} - , - ); - } - return {dnaBlocks}; -}; diff --git a/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierBlocks.tsx b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierBlocks.tsx new file mode 100644 index 0000000000..1d465d3e5a --- /dev/null +++ b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierBlocks.tsx @@ -0,0 +1,59 @@ +import { useBackend } from '../../backend'; +import { Box, Button, Flex } from '../../components'; + +export const DNAModifierBlocks = (props: { + dnaString: string; + selectedBlock: number; + selectedSubblock: number; + blockSize: number; + action: string; +}) => { + const { act } = useBackend(); + + const { dnaString, selectedBlock, selectedSubblock, blockSize, action } = + props; + + const characters: string[] = dnaString.split(''); + let dnaBlocks: React.JSX.Element[] = []; + for (let block = 0; block < characters.length; block += blockSize) { + const realBlock: number = block / blockSize + 1; + let subBlocks: React.JSX.Element[] = []; + for (let subblock = 0; subblock < blockSize; subblock++) { + const realSubblock: number = subblock + 1; + subBlocks.push( + , + ); + } + dnaBlocks.push( + + + {realBlock} + + {subBlocks} + , + ); + } + return {dnaBlocks}; +}; diff --git a/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierIrradiating.tsx b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierIrradiating.tsx new file mode 100644 index 0000000000..1b6c04d76e --- /dev/null +++ b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierIrradiating.tsx @@ -0,0 +1,22 @@ +import { Box, Dimmer, Icon } from '../../components'; + +export const DNAModifierIrradiating = (props: { duration: number }) => { + return ( + + +
+ +

+ +  Irradiating occupant  + +

+
+ +

+ For {props.duration} second{props.duration === 1 ? '' : 's'} +

+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierMain.tsx b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierMain.tsx new file mode 100644 index 0000000000..aa08e16013 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierMain.tsx @@ -0,0 +1,265 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../../backend'; +import { + Box, + Button, + Flex, + Icon, + Knob, + LabeledList, + Section, + Tabs, +} from '../../components'; +import { operations, rejuvenatorsDoses } from './constants'; +import { DNAModifierBlocks } from './DNAModifierBlocks'; +import { DNAModifierMainBuffers } from './DNAModifierMainBuffers'; +import { Data } from './types'; + +export const DNAModifierMain = (props: { isDNAInvalid: BooleanLike }) => { + const { act, data } = useBackend(); + + const { selectedMenuKey, hasOccupant } = data; + + if (!hasOccupant) { + return ( +
+ + + +
+ No occupant in DNA modifier. +
+
+
+ ); + } else if (props.isDNAInvalid) { + return ( +
+ + + +
+ No operation possible on this subject. +
+
+
+ ); + } + let body; + if (selectedMenuKey === 'ui') { + body = ( + <> + + + + ); + } else if (selectedMenuKey === 'se') { + body = ( + <> + + + + ); + } else if (selectedMenuKey === 'buffer') { + body = ; + } else if (selectedMenuKey === 'rejuvenators') { + body = ; + } + return ( +
+ + {operations.map((op, i) => ( + act('selectMenuKey', { key: op[0] })} + > + + {op[1]} + + ))} + + {body} +
+ ); +}; + +const DNAModifierMainUI = (props) => { + const { act, data } = useBackend(); + + const { + selectedUIBlock, + selectedUISubBlock, + selectedUITarget, + dnaBlockSize, + occupant, + } = data; + + return ( +
+ + + + value.toString(16).toUpperCase()} + ml="0" + onChange={(e, val) => act('changeUITarget', { value: val })} + /> + + + +
+ ); +}; + +const DNAModifierMainSE = (props) => { + const { act, data } = useBackend(); + + const { selectedSEBlock, selectedSESubBlock, dnaBlockSize, occupant } = data; + + return ( +
+ + +
+ ); +}; + +const DNAModifierMainRadiationEmitter = (props) => { + const { act, data } = useBackend(); + + const { radiationIntensity, radiationDuration } = data; + + return ( +
+ + + act('radiationIntensity', { value: val })} + /> + + + act('radiationDuration', { value: val })} + /> + + + +
+ ); +}; + +const DNAModifierMainRejuvenators = (props) => { + const { act, data } = useBackend(); + + const { isBeakerLoaded, beakerVolume, beakerLabel } = data; + + return ( +
act('ejectBeaker')} + > + Eject + + } + > + {isBeakerLoaded ? ( + + + {rejuvenatorsDoses.map((a, i) => ( + + ))} + + + + {beakerLabel ? beakerLabel : 'No label'} + {beakerVolume ? ( + + {beakerVolume} unit{beakerVolume === 1 ? '' : 's'} remaining + + ) : ( + Empty + )} + + + ) : ( + + +
+ No beaker loaded. +
+ )} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierMainBuffers.tsx b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierMainBuffers.tsx new file mode 100644 index 0000000000..d3efe7678d --- /dev/null +++ b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierMainBuffers.tsx @@ -0,0 +1,254 @@ +import { useBackend } from '../../backend'; +import { Box, Button, Icon, LabeledList, Section } from '../../components'; +import { buffData, Data } from './types'; + +export const DNAModifierMainBuffers = (props) => { + const { data } = useBackend(); + + const { buffers } = data; + + let bufferElements = buffers.map((buffer, i) => ( + + )); + return ( + <> +
{bufferElements}
+ + + ); +}; + +const DNAModifierMainBuffersElement = (props: { + id: number; + name: string; + buffer: buffData; +}) => { + const { act, data } = useBackend(); + const { id, name, buffer } = props; + const { isInjectorReady } = data; + const realName: string = name + (buffer.data ? ' - ' + buffer.label : ''); + return ( + +
+ + act('bufferOption', { + option: 'clear', + id: id, + }) + } + > + Clear + + + + + } + > + + + + + + + + {!!buffer.data && ( + <> + + {buffer.owner || Unknown} + + + {buffer.type === 'ui' + ? 'Unique Identifiers' + : 'Structural Enzymes'} + {!!buffer.ue && ' and Unique Enzymes'} + + + + + + + + )} + + {!buffer.data && ( + + This buffer is empty. + + )} +
+
+ ); +}; + +const DNAModifierMainBuffersDisk = (props) => { + const { act, data } = useBackend(); + const { hasDisk, disk } = data; + return ( +
+ act('wipeDisk')} + > + Wipe + + + + } + > + {hasDisk ? ( + disk.data ? ( + + + {disk.label ? disk.label : 'No label'} + + + {disk.owner ? disk.owner : Unknown} + + + {disk.type === 'ui' ? 'Unique Identifiers' : 'Structural Enzymes'} + {!!disk.ue && ' and Unique Enzymes'} + + + ) : ( + Disk is blank. + ) + ) : ( + + +
+ No disk inserted. +
+ )} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierOccupant.tsx b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierOccupant.tsx new file mode 100644 index 0000000000..b8fd28b5ce --- /dev/null +++ b/tgui/packages/tgui/interfaces/DNAModifier/DNAModifierOccupant.tsx @@ -0,0 +1,103 @@ +import { BooleanLike } from 'common/react'; + +import { useBackend } from '../../backend'; +import { + Box, + Button, + Icon, + LabeledList, + ProgressBar, + Section, +} from '../../components'; +import { stats } from './constants'; +import { Data } from './types'; + +export const DNAModifierOccupant = (props: { isDNAInvalid: BooleanLike }) => { + const { act, data } = useBackend(); + + const { locked, hasOccupant, occupant } = data; + + return ( +
+ + Door Lock: + + + + + } + > + {hasOccupant ? ( + <> + + + {occupant.name} + + + + + {stats[occupant.stat!][1]} + + + + + {props.isDNAInvalid ? ( + + +   The occupant's DNA structure is ruined beyond + recognition, please insert a subject with an intact DNA structure. + + ) : ( + + + + + + {data.occupant.uniqueEnzymes ? ( + data.occupant.uniqueEnzymes + ) : ( + + +   Unknown + + )} + + + )} + + ) : ( + Cell unoccupied. + )} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/DNAModifier/constants.ts b/tgui/packages/tgui/interfaces/DNAModifier/constants.ts new file mode 100644 index 0000000000..d247c8a51a --- /dev/null +++ b/tgui/packages/tgui/interfaces/DNAModifier/constants.ts @@ -0,0 +1,14 @@ +export const stats: string[][] = [ + ['good', 'Alive'], + ['average', 'Unconscious'], + ['bad', 'DEAD'], +]; + +export const operations: string[][] = [ + ['ui', 'Modify U.I.', 'dna'], + ['se', 'Modify S.E.', 'dna'], + ['buffer', 'Transfer Buffers', 'syringe'], + ['rejuvenators', 'Rejuvenators', 'flask'], +]; + +export const rejuvenatorsDoses: number[] = [5, 10, 20, 30, 50]; diff --git a/tgui/packages/tgui/interfaces/DNAModifier/index.tsx b/tgui/packages/tgui/interfaces/DNAModifier/index.tsx new file mode 100644 index 0000000000..220f72de66 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DNAModifier/index.tsx @@ -0,0 +1,29 @@ +import { useBackend } from '../../backend'; +import { Window } from '../../layouts'; +import { ComplexModal } from '../common/ComplexModal'; +import { DNAModifierIrradiating } from './DNAModifierIrradiating'; +import { DNAModifierMain } from './DNAModifierMain'; +import { DNAModifierOccupant } from './DNAModifierOccupant'; +import { Data } from './types'; + +export const DNAModifier = (props) => { + const { data } = useBackend(); + + const { irradiating, occupant } = data; + + const isDNAInvalid: boolean = + !occupant.isViableSubject || + !occupant.uniqueIdentity || + !occupant.structuralEnzymes; + + return ( + + + {irradiating && } + + + + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/DNAModifier/types.ts b/tgui/packages/tgui/interfaces/DNAModifier/types.ts new file mode 100644 index 0000000000..73907fe9e7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DNAModifier/types.ts @@ -0,0 +1,54 @@ +import { BooleanLike } from 'common/react'; + +export type Data = { + selectedMenuKey: string; + locked: BooleanLike; + hasOccupant: BooleanLike; + isInjectorReady: BooleanLike; + hasDisk: BooleanLike; + disk: buffData; + buffers: buffData[]; + radiationIntensity: number; + radiationDuration: number; + irradiating: number; + dnaBlockSize: number; + selectedUIBlock: number; + selectedUISubBlock: number; + selectedSEBlock: number; + selectedSESubBlock: number; + selectedUITarget: number; + selectedUITargetHex: string; + occupant: { + name: string | null; + stat: number | null; + isViableSubject: BooleanLike | null; + health: number | null; + maxHealth: number | null; + minHealth: number | null; + uniqueEnzymes: string | null; + uniqueIdentity: string | null; + structuralEnzymes: string | null; + radiationLevel: number | null; + }; + isBeakerLoaded: BooleanLike; + beakerLabel: string | null; + beakerVolume: number; + modal: modalData; +}; + +type modalData = { + id: string; + text: string; + args: { + id: string; + }; + modal_type: string; +}; + +export type buffData = { + data: number[] | null; + owner: string | null; + label: string | null; + type: string | null; + ue: BooleanLike; +}; diff --git a/tgui/packages/tgui/interfaces/DestinationTagger.jsx b/tgui/packages/tgui/interfaces/DestinationTagger.jsx deleted file mode 100644 index ee46aa1fba..0000000000 --- a/tgui/packages/tgui/interfaces/DestinationTagger.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useBackend } from '../backend'; -import { Button, Flex, Section } from '../components'; -import { Window } from '../layouts'; - -export const DestinationTagger = (props) => { - const { act, data } = useBackend(); - - const { currTag, taggerLocs } = data; - - return ( - - -
- - {taggerLocs.sort().map((tag) => ( - - - - ))} - -
-
-
- ); -}; diff --git a/tgui/packages/tgui/interfaces/DestinationTagger.tsx b/tgui/packages/tgui/interfaces/DestinationTagger.tsx new file mode 100644 index 0000000000..a55eef5fc4 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DestinationTagger.tsx @@ -0,0 +1,50 @@ +import { useBackend } from '../backend'; +import { Button, Flex, Section } from '../components'; +import { Window } from '../layouts'; + +type Data = { + currTag: string; + taggerLevels: { z: number; location: string }[]; + taggerLocs: { tag: string; level: number }[]; +}; + +export const DestinationTagger = (props) => { + const { act, data } = useBackend(); + + const { currTag, taggerLevels = [], taggerLocs } = data; + + const unique_levels = taggerLevels.filter((obj, index) => { + return index === taggerLevels.findIndex((o) => obj.location === o.location); + }); + + return ( + + +
+ {unique_levels.map((level) => ( +
+ + {taggerLocs.map( + (tag) => + level.z === tag.level && ( + + + + ), + )} + +
+ ))} +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/DiseaseSplicer.jsx b/tgui/packages/tgui/interfaces/DiseaseSplicer.tsx similarity index 76% rename from tgui/packages/tgui/interfaces/DiseaseSplicer.jsx rename to tgui/packages/tgui/interfaces/DiseaseSplicer.tsx index 4db3567cff..f962aa1628 100644 --- a/tgui/packages/tgui/interfaces/DiseaseSplicer.jsx +++ b/tgui/packages/tgui/interfaces/DiseaseSplicer.tsx @@ -1,9 +1,24 @@ +import { BooleanLike } from 'common/react'; + import { useBackend } from '../backend'; import { Box, Button, LabeledList, ProgressBar, Section } from '../components'; import { Window } from '../layouts'; +type Data = { + dish_inserted: BooleanLike; + buffer: { name: string; stage: number } | null; + species_buffer: string | null; + busy: string | null; + growth: number; + effects: + | { name: string; stage: number; reference: string; badness: number }[] + | null; + info: string; + affected_species: string[] | null; +}; + export const DiseaseSplicer = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { busy } = data; @@ -28,7 +43,7 @@ export const DiseaseSplicer = (props) => { }; const DiseaseSplicerVirusDish = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); const { dish_inserted, effects, info, growth, affected_species } = data; @@ -60,12 +75,12 @@ const DiseaseSplicerVirusDish = (props) => { {info ? ( -
+
{info}
) : ( <> -
+
{(effects && effects.map((effect) => ( @@ -74,23 +89,25 @@ const DiseaseSplicerVirusDish = (props) => { ))) || No virus sample loaded.}
-
- {!affected_species || !affected_species.length ? 'None' : null} - {affected_species.sort().join(', ')} +
+ {!affected_species || !affected_species.length + ? 'None' + : affected_species.sort().join(', ')}
-
+
CAUTION: Reverse engineering will destroy the viral sample. - {effects.map((e) => ( - - ))} + {effects && + effects.map((e) => ( + + ))} @@ -102,18 +119,9 @@ const DiseaseSplicerVirusDish = (props) => { }; const DiseaseSplicerStorage = (props) => { - const { act, data } = useBackend(); + const { act, data } = useBackend(); - const { - dish_inserted, - buffer, - species_buffer, - effects, - info, - growth, - affected_species, - busy, - } = data; + const { buffer, species_buffer, info } = data; return (
@@ -173,7 +181,7 @@ const DiseaseSplicerStorage = (props) => {
act('set_dcall_area', { area: val })} diff --git a/tgui/packages/tgui/interfaces/EmbeddedController.jsx b/tgui/packages/tgui/interfaces/EmbeddedController.jsx deleted file mode 100644 index 8c52e3514e..0000000000 --- a/tgui/packages/tgui/interfaces/EmbeddedController.jsx +++ /dev/null @@ -1,757 +0,0 @@ -import { useBackend } from '../backend'; -import { - Box, - Button, - Flex, - Icon, - LabeledList, - ProgressBar, - Section, -} from '../components'; -import { Window } from '../layouts'; -import { createLogger } from '../logging'; -const logger = createLogger('fuck'); - -// This UI uses an internal routing system for the many different variants of -// embedded controllers in use. -let primaryRoutes = {}; - -/** - * This is an all-in-one replacement for the following NanoUI Templates: - * - advanced_airlock_console.tmpl - * - docking_airlock_console.tmpl - * - door_access_console.tmpl - * - escape_pod_console.tmpl - * - escape_pod_berth_console.tmpl - * - multi_docking_console.tmpl - * - phoron_airlock_console.tmpl - * - simple_airlock_console.tmpl - * - simple_docking_console.tmpl - * - simple_docking_console_pod.tmpl -- Funny enough, wasn't used anywhere. - */ - -/** - * Let's cover all of the attributes of `data` for this UI right here. - * For those unfamiliar with JSDoc syntax, [param] indicates - * an optional parameter. - */ - -/** - * Interior/Exterior Door Status - * @typedef {Object} doorStatus - * @property {('open'|'closed')} state - * @property {('locked'|'unlocked')} lock - */ - -/** - * Dock Status - * @typedef {('undocked'|'undocking'|'docking'|'docked')} dockStatus - */ - -/** - * All possible data attributes. - * @typedef {Object} Data - * @property {string} internalTemplateName -