diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 45682f184d7..024c62cec93 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -1627,3 +1627,47 @@ GLOBAL_DATUM_INIT(dview_mob, /mob/dview, new) return call(source, proctype)(arglist(arguments)) #define TURF_FROM_COORDS_LIST(List) (locate(List[1], List[2], List[3])) + +// Converts browser keycodes to BYOND keycodes. +/proc/browser_keycode_to_byond(keycode) + keycode = text2num(keycode) + switch(keycode) + // letters and numbers + if(65 to 90, 48 to 57) + return ascii2text(keycode) + if(17) + return "Ctrl" + if(18) + return "Alt" + if(16) + return "Shift" + if(37) + return "West" + if(38) + return "North" + if(39) + return "East" + if(40) + return "South" + if(45) + return "Insert" + if(46) + return "Delete" + if(36) + return "Northwest" + if(35) + return "Southwest" + if(33) + return "Northeast" + if(34) + return "Southeast" + if(112 to 123) + return "F[keycode-111]" + if(96 to 105) + return "Numpad[keycode-96]" + if(188) + return "," + if(190) + return "." + if(189) + return "-" diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 1017603ff37..9f94d9ee7b0 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -121,7 +121,10 @@ Class Procs: var/market_verb = "Customer" var/payment_department = ACCOUNT_ENG - var/ui_x //For storing and overriding ui dimensions + // For storing and overriding ui id and dimensions + var/tgui_id // ID of TGUI interface + var/ui_style // ID of custom TGUI style (optional) + var/ui_x // Default size of TGUI window, in pixels var/ui_y /obj/machinery/Initialize() diff --git a/code/game/objects/items/RPD.dm b/code/game/objects/items/RPD.dm index 2ec13890345..6ee539d7271 100644 --- a/code/game/objects/items/RPD.dm +++ b/code/game/objects/items/RPD.dm @@ -174,7 +174,7 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list( icon_state = "[icon_state]_preview" /obj/item/pipe_dispenser - name = "Rapid Piping Device (RPD)" + name = "Rapid Pipe Dispenser (RPD)" desc = "A device used to rapidly pipe things." icon = 'icons/obj/tools.dmi' icon_state = "rpd" @@ -245,7 +245,7 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list( var/datum/asset/assets = get_asset_datum(/datum/asset/spritesheet/pipes) assets.send(user) - ui = new(user, src, ui_key, "rpd", name, 300, 550, master_ui, state) + ui = new(user, src, ui_key, "rpd", name, 425, 472, master_ui, state) ui.open() /obj/item/pipe_dispenser/ui_data(mob/user) @@ -316,16 +316,14 @@ GLOBAL_LIST_INIT(transit_tube_recipes, list( playeffect = FALSE if("mode") var/n = text2num(params["mode"]) - if(n == 2 && !(mode&1) && !(mode&2)) - mode |= 3 - else if(mode&n) + if(mode & n) mode &= ~n else mode |= n - if(playeffect) spark_system.start() playsound(get_turf(src), 'sound/effects/pop.ogg', 50, FALSE) + return TRUE /obj/item/pipe_dispenser/pre_attack(atom/A, mob/user) if(!user.IsAdvancedToolUser() || istype(A, /turf/open/space/transit)) diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm index d15a362bbc1..26a5a2f3717 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm @@ -6,6 +6,8 @@ anchored = FALSE density = TRUE resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + + ui_style = "nanotrasen" ui_x = 500 ui_y = 600 @@ -13,7 +15,6 @@ var/default_timer_set = 90 var/minimum_timer_set = 90 var/maximum_timer_set = 3600 - var/ui_style = "nanotrasen" var/numeric_input = "" var/timing = FALSE diff --git a/code/modules/cargo/exports/tools.dm b/code/modules/cargo/exports/tools.dm index 9e58e4ba954..7bee3017d9f 100644 --- a/code/modules/cargo/exports/tools.dm +++ b/code/modules/cargo/exports/tools.dm @@ -108,7 +108,7 @@ /datum/export/rpd cost = 100 - unit_name = "rapid piping device" + unit_name = "rapid pipe dispenser" export_types = list(/obj/item/pipe_dispenser) /datum/export/singulo //failsafe in case someone decides to ship a live singularity to CentCom without the corresponding bounty diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 474a861a5c2..aafc431c489 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -81,12 +81,24 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( //Logs all hrefs, except chat pings if(!(href_list["_src_"] == "chat" && href_list["proc"] == "ping" && LAZYLEN(href_list) == 2)) log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]") - + //byond bug ID:2256651 if (asset_cache_job && asset_cache_job in completed_asset_jobs) to_chat(src, "An error has been detected in how your client is receiving resources. Attempting to correct.... (If you keep seeing these messages you might want to close byond and reconnect)") src << browse("...", "window=asset_cache_browser") + // Keypress passthrough + if(href_list["__keydown"]) + var/keycode = browser_keycode_to_byond(href_list["__keydown"]) + if(keycode) + keyDown(keycode) + return + if(href_list["__keyup"]) + var/keycode = browser_keycode_to_byond(href_list["__keyup"]) + if(keycode) + keyUp(keycode) + return + // Admin PM if(href_list["priv_msg"]) cmd_admin_pm(href_list["priv_msg"],null) diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm index ccfddd3999e..d15dc610c33 100644 --- a/code/modules/modular_computers/computers/item/computer.dm +++ b/code/modules/modular_computers/computers/item/computer.dm @@ -318,7 +318,7 @@ data["PC_batteryicon"] = "batt_20.gif" else data["PC_batteryicon"] = "batt_5.gif" - data["PC_batterypercent"] = "[round(battery_module.battery.percent())] %" + data["PC_batterypercent"] = "[round(battery_module.battery.percent())]%" data["PC_showbatteryicon"] = 1 else data["PC_batteryicon"] = "batt_5.gif" diff --git a/code/modules/modular_computers/computers/item/computer_ui.dm b/code/modules/modular_computers/computers/item/computer_ui.dm index df3000d0fcc..18c6d7a5a0e 100644 --- a/code/modules/modular_computers/computers/item/computer_ui.dm +++ b/code/modules/modular_computers/computers/item/computer_ui.dm @@ -37,8 +37,8 @@ if (!ui) var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers) assets.send(user) - - ui = new(user, src, ui_key, "ntos_main", "NTOS Main menu", 400, 500, master_ui, state) + ui = new(user, src, ui_key, "ntos_main", "NtOS Main menu", 400, 500, master_ui, state) + ui.set_style("ntos") ui.open() ui.set_autoupdate(state = 1) @@ -68,10 +68,10 @@ switch(action) if("PC_exit") kill_program() - return 1 + return TRUE if("PC_shutdown") shutdown_computer() - return 1 + return TRUE if("PC_minimize") var/mob/user = usr if(!active_program || !all_components[MC_CPU]) @@ -142,6 +142,7 @@ set_light(comp_light_luminosity, 1, comp_light_color) else set_light(0) + return TRUE if("PC_light_color") var/mob/user = usr @@ -156,6 +157,7 @@ comp_light_color = new_color light_color = new_color update_light() + return TRUE else return diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm index f94ac6cc065..95c635b771f 100644 --- a/code/modules/modular_computers/file_system/program.dm +++ b/code/modules/modular_computers/file_system/program.dm @@ -16,9 +16,9 @@ var/network_destination = null // Optional string that describes what NTNet server/system this program connects to. Used in default logging. var/available_on_ntnet = 1 // Whether the program can be downloaded from NTNet. Set to 0 to disable. var/available_on_syndinet = 0 // Whether the program can be downloaded from SyndiNet (accessible via emagging the computer). Set to 1 to enable. - var/tgui_id // ID of TG UI interface - var/ui_style // ID of custom TG UI style (optional) - var/ui_x = 575 // Default size of TG UI window, in pixels + var/tgui_id // ID of TGUI interface + var/ui_style // ID of custom TGUI style (optional) + var/ui_x = 575 // Default size of TGUI window, in pixels var/ui_y = 700 var/ui_header = null // Example: "something.gif" - a header image that will be rendered in computer's UI when this program is running at background. Images are taken from /icons/program_icons. Be careful not to use too large images! diff --git a/code/modules/modular_computers/file_system/programs/powermonitor.dm b/code/modules/modular_computers/file_system/programs/powermonitor.dm index 0439438cf0e..f7c734667b0 100644 --- a/code/modules/modular_computers/file_system/programs/powermonitor.dm +++ b/code/modules/modular_computers/file_system/programs/powermonitor.dm @@ -12,8 +12,9 @@ network_destination = "power monitoring system" size = 9 tgui_id = "ntos_power_monitor" - ui_x = 1200 - ui_y = 1000 + ui_style = "ntos" + ui_x = 550 + ui_y = 700 var/has_alert = 0 var/obj/structure/cable/attached_wire diff --git a/code/modules/modular_computers/file_system/programs/sm_monitor.dm b/code/modules/modular_computers/file_system/programs/sm_monitor.dm index bd2426a294d..1af0b6f96c2 100644 --- a/code/modules/modular_computers/file_system/programs/sm_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/sm_monitor.dm @@ -9,8 +9,9 @@ network_destination = "supermatter monitoring system" size = 5 tgui_id = "ntos_supermatter_monitor" + ui_style = "ntos" ui_x = 600 - ui_y = 400 + ui_y = 350 var/last_status = SUPERMATTER_INACTIVE var/list/supermatters var/obj/machinery/power/supermatter_crystal/active // Currently selected supermatter crystal. diff --git a/code/modules/power/monitor.dm b/code/modules/power/monitor.dm index e0e34402952..5b1f8fb7276 100644 --- a/code/modules/power/monitor.dm +++ b/code/modules/power/monitor.dm @@ -10,8 +10,10 @@ idle_power_usage = 20 active_power_usage = 100 circuit = /obj/item/circuitboard/computer/powermonitor - ui_x = 1200 - ui_y = 1000 + tgui_id = "power_monitor" + ui_style = "ntos" + ui_x = 550 + ui_y = 700 var/obj/structure/cable/attached_wire var/obj/machinery/power/apc/local_apc @@ -87,7 +89,7 @@ datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) if(!ui) - ui = new(user, src, ui_key, "power_monitor", name, ui_x, ui_y, master_ui, state) + ui = new(user, src, ui_key, tgui_id, name, ui_x, ui_y, master_ui, state) ui.open() /obj/machinery/computer/monitor/ui_data() diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index 6faf4aed69a..91e5ae08379 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -42,7 +42,6 @@ var/datum/tgui/master_ui /// Children of this UI. var/list/datum/tgui/children = list() - var/titlebar = TRUE var/custom_browser_id = FALSE var/ui_screen = "home" @@ -262,6 +261,9 @@ ui_screen = params["screen"] SStgui.update_uis(src_object) if("tgui:log") + // Force window to show frills on fatal errors + if(params["fatal"]) + winset(user, window_id, "titlebar=1;can-resize=1;size=600x600") log_message(params["log"]) if("tgui:link") user << link(params["url"]) @@ -357,8 +359,5 @@ if(status == UI_DISABLED || push) // Update if the UI just because disabled, or a push is requested. push_data(null, force = TRUE) -/datum/tgui/proc/set_titlebar(value) - titlebar = value - /datum/tgui/proc/log_message(message) log_tgui("[user] ([user.ckey]) using \"[title]\":\n[message]") diff --git a/tgui-next/README.md b/tgui-next/README.md index 24733e36bff..3ee91752eb3 100644 --- a/tgui-next/README.md +++ b/tgui-next/README.md @@ -292,11 +292,23 @@ over the button. - `bottom` (default) - Show tooltip below the button. - `left` - Show tooltip on the left of the button. - `right` - Show tooltip on the right of the button. +- `ellipsis: boolean` - If button width is constrained, button text will +be truncated with an ellipsis. Be careful however, because this prop breaks +the baseline alignment. - `title: string` - A native browser tooltip, which appears when hovering over the button. - `content/children: any` - Content to render inside the button. - `onClick: function` - Called when element is clicked. +### `Button.Checkbox` + +A ghetto checkbox, made entirely using existing Button API. + +Props: + +- See inherited props: [Button](#button) +- `checked: boolean` - Boolean value, which marks the checkbox as checked. + ### `ColorBox` Displays a 1-character wide colored square. Can be used as a status indicator, @@ -349,6 +361,10 @@ two flex items as far as possible from each other. Props: - See inherited props: [Box](#box) +- `spacing: number` - Spacing between flex items, in integer units +(1 unit - 0.5em). Does not directly relate to a flex css property +(adds a modifier class under the hood), and only integer numbers are +supported. - `direction: string` - This establishes the main-axis, thus defining the direction flex items are placed in the flex container. - `row` (default) - left to right. @@ -432,7 +448,7 @@ Props: Props: -- See inherited props: [Table.Cell](#table-cell) +- See inherited props: [Table.Cell](#tablecell) - `size: number` (default: 1) - Size of the column relative to other columns. ### `Icon` diff --git a/tgui-next/packages/common/collections.js b/tgui-next/packages/common/collections.js index a679aba217b..bbcea64cd88 100644 --- a/tgui-next/packages/common/collections.js +++ b/tgui-next/packages/common/collections.js @@ -35,7 +35,11 @@ export const map = iterateeFn => collection => { return collection; } if (Array.isArray(collection)) { - return collection.map(iterateeFn); + const result = []; + for (let i = 0; i < collection.length; i++) { + result.push(iterateeFn(collection[i], i, collection)); + } + return result; } if (typeof collection === 'object') { const hasOwnProperty = Object.prototype.hasOwnProperty; @@ -97,20 +101,11 @@ export const sortBy = (...iterateeFns) => array => { }; /** - * A fast implementation of map. + * A version of map, but for mapping over two arrays instead of one. + * The iteratee is invoked with three arguments: + * (valueA, valueB, index). */ -export const fastMap = (array, iterateeFn) => { - const result = []; - for (let i = 0; i < array.length; i++) { - result.push(iterateeFn(array[i], i)); - } - return result; -}; - -/** - * A version of fastMap, but for mapping over two arrays instead of one. - */ -export const fastProduct = (arrayA, arrayB, iterateeFn) => { +export const product = iterateeFn => (arrayA, arrayB) => { const result = []; for (let i = 0; i < arrayA.length; i++) { result.push(iterateeFn(arrayA[i], arrayB[i], i)); @@ -121,7 +116,7 @@ export const fastProduct = (arrayA, arrayB, iterateeFn) => { /** * A fast implementation of reduce. */ -export const fastReduce = (array, reducerFn, initialValue) => { +export const reduce = (reducerFn, initialValue) => array => { const length = array.length; let i; let result; @@ -138,3 +133,34 @@ export const fastReduce = (array, reducerFn, initialValue) => { } return result; }; + +/** + * Creates an array of grouped elements, the first of which contains + * the first elements of the given arrays, the second of which contains + * the second elements of the given arrays, and so on. + */ +export const zip = (...arrays) => { + if (arrays.length === 0) { + return; + } + const numArrays = arrays.length; + const numValues = arrays[0].length; + const result = []; + for (let valueIndex = 0; valueIndex < numValues; valueIndex++) { + const entry = []; + for (let arrayIndex = 0; arrayIndex < numArrays; arrayIndex++) { + entry.push(arrays[arrayIndex][valueIndex]); + } + result.push(entry); + } + return result; +}; + +/** + * This method is like "zip" except that it accepts iteratee to + * specify how grouped values should be combined. The iteratee is + * invoked with the elements of each group. + */ +export const zipWith = iterateeFn => (...arrays) => { + return map(values => iterateeFn(...values))(zip(...arrays)); +}; diff --git a/tgui-next/packages/common/fp.js b/tgui-next/packages/common/fp.js index 8bed9781e20..4be45877e2c 100644 --- a/tgui-next/packages/common/fp.js +++ b/tgui-next/packages/common/fp.js @@ -39,6 +39,3 @@ export const compose = (...funcs) => { return funcs.reduce((a, b) => (value, ...rest) => a(b(value, ...rest), ...rest)); }; - -// TODO: Refactor all code which uses this export -export { map } from './collections'; diff --git a/tgui-next/packages/common/vector.js b/tgui-next/packages/common/vector.js index 7819d531619..967827f556e 100644 --- a/tgui-next/packages/common/vector.js +++ b/tgui-next/packages/common/vector.js @@ -1,4 +1,4 @@ -import { fastMap, fastProduct, fastReduce } from './collections'; +import { map, product, reduce } from './collections'; /** * Creates a vector, with as many dimensions are there are arguments. @@ -20,23 +20,23 @@ class Vector { } add(vec) { - return new Vector(fastProduct(this.c, vec.c, ADD)); + return new Vector(product(ADD)(this.c, vec.c)); } subtract(vec) { - return new Vector(fastProduct(this.c, vec.c, SUB)); + return new Vector(product(SUB)(this.c, vec.c)); } multiply(n) { - return new Vector(fastMap(this.c, x => x * n)); + return new Vector(map(x => x * n)(this.c)); } divide(n) { - return new Vector(fastMap(this.c, x => x / n)); + return new Vector(map(x => x / n)(this.c)); } magnitude() { - return Math.sqrt(fastReduce(fastProduct(this.c, this.c, MUL), ADD)); + return Math.sqrt(reduce(ADD)(product(MUL)(this.c, this.c))); } normalize() { diff --git a/tgui-next/packages/tgui/public/images/tgui-bg-nanotrasen.svg b/tgui-next/packages/tgui/assets/bg-nanotrasen.svg similarity index 100% rename from tgui-next/packages/tgui/public/images/tgui-bg-nanotrasen.svg rename to tgui-next/packages/tgui/assets/bg-nanotrasen.svg diff --git a/tgui-next/packages/tgui/public/images/tgui-bg-syndicate.svg b/tgui-next/packages/tgui/assets/bg-syndicate.svg similarity index 100% rename from tgui-next/packages/tgui/public/images/tgui-bg-syndicate.svg rename to tgui-next/packages/tgui/assets/bg-syndicate.svg diff --git a/tgui-next/packages/tgui/components/Box.js b/tgui-next/packages/tgui/components/Box.js index 41c418e5632..08b4ab72506 100644 --- a/tgui-next/packages/tgui/components/Box.js +++ b/tgui-next/packages/tgui/components/Box.js @@ -83,6 +83,16 @@ const styleMapperByPropName = { color: mapColorPropTo('color'), textColor: mapColorPropTo('color'), backgroundColor: mapColorPropTo('background-color'), + // Utility props + fillPositionedParent: (style, value) => { + if (value) { + style['position'] = 'absolute'; + style['top'] = 0; + style['bottom'] = 0; + style['left'] = 0; + style['right'] = 0; + } + }, }; export const computeBoxProps = props => { @@ -124,6 +134,7 @@ export const Box = props => { ...rest } = props; const color = props.textColor || props.color; + const backgroundColor = props.backgroundColor; // Render props if (typeof children === 'function') { return children(computeBoxProps(props)); @@ -136,6 +147,7 @@ export const Box = props => { classes([ className, isColorClass(color) && 'color-' + color, + isColorClass(backgroundColor) && 'color-bg-' + backgroundColor, ]), content || children, ChildFlags.UnknownChildren, diff --git a/tgui-next/packages/tgui/components/Button.js b/tgui-next/packages/tgui/components/Button.js index dfd8983b091..36aa3ca69b1 100644 --- a/tgui-next/packages/tgui/components/Button.js +++ b/tgui-next/packages/tgui/components/Button.js @@ -19,6 +19,7 @@ export const Button = props => { selected, tooltip, tooltipPosition, + ellipsis, content, children, onclick, @@ -43,9 +44,10 @@ export const Button = props => { disabled && 'Button--disabled', selected && 'Button--selected', hasContent && 'Button--hasContent', + ellipsis && 'Button--ellipsis', (color && typeof color === 'string') ? 'Button--color--' + color - : 'Button--color--normal', + : 'Button--color--default', className, ])} tabIndex={!disabled && '0'} @@ -89,3 +91,16 @@ export const Button = props => { }; Button.defaultHooks = pureComponentHooks; + +export const ButtonCheckbox = props => { + const { checked, ...rest } = props; + return ( + + + + )} +
+ + {programs.map(program => ( + + +
+
+ + ); +}; diff --git a/tgui-next/packages/tgui/interfaces/NtosSupermatterMonitor.js b/tgui-next/packages/tgui/interfaces/NtosSupermatterMonitor.js new file mode 100644 index 00000000000..3fb9aeb66d0 --- /dev/null +++ b/tgui-next/packages/tgui/interfaces/NtosSupermatterMonitor.js @@ -0,0 +1,179 @@ +import { sortBy } from 'common/collections'; +import { flow } from 'common/fp'; +import { toFixed } from 'common/math'; +import { act } from '../byond'; +import { Box, Button, Flex, LabeledList, ProgressBar, Section, Table } from '../components'; +import { getGasColor, getGasLabel } from '../constants'; + +const logScale = value => Math.log2(16 + Math.max(0, value)) - 4; + +export const NtosSupermatterMonitor = props => { + const { state } = props; + const { config, data } = state; + const { ref } = config; + const { + active, + SM_integrity, + SM_power, + SM_ambienttemp, + SM_ambientpressure, + } = data; + if (!active) { + return ( + + ); + } + const gases = flow([ + gases => gases.filter(gas => gas.amount >= 0.01), + sortBy(gas => -gas.amount), + ])(data.gases || []); + const gasMaxAmount = Math.max(1, ...gases.map(gas => gas.amount)); + return ( + + +
+ + + + + + + {toFixed(SM_power) + ' MeV/cm3'} + + + + + {toFixed(SM_ambienttemp) + ' K'} + + + + + {toFixed(SM_ambientpressure) + ' kPa'} + + + +
+
+ +
act(ref, 'PRG_clear')} /> + )}> + + + {gases.map(gas => ( + + + {toFixed(gas.amount, 2) + '%'} + + + ))} + + +
+
+
+ ); +}; + +/** + * A hack to force certain things (like tables) to position correctly + * inside bugged things, like Flex in Internet Explorer. + */ +const ForcedBox = props => { + const { height, children } = props; + return ( + + + {children} + + + ); +}; + +const SupermatterList = props => { + const { state } = props; + const { config, data } = state; + const { ref } = config; + const { supermatters = [] } = data; + return ( +
act(ref, 'PRG_refresh')} /> + )}> + + {supermatters.map(sm => ( + + + {sm.uid + '. ' + sm.area_name} + + + Integrity: + + + + + +
+
+ ); +}; diff --git a/tgui-next/packages/tgui/interfaces/NtosWrapper.js b/tgui-next/packages/tgui/interfaces/NtosWrapper.js new file mode 100644 index 00000000000..2aea1d5d578 --- /dev/null +++ b/tgui-next/packages/tgui/interfaces/NtosWrapper.js @@ -0,0 +1,110 @@ +import { act } from '../byond'; +import { Box, Button } from '../components'; +import { refocusLayout } from '../refocus'; + +export const NtosWrapper = props => { + const { state, children } = props; + const { config, data } = state; + const { ref } = config; + const { + PC_batteryicon, + PC_showbatteryicon, + PC_batterypercent, + PC_ntneticon, + PC_apclinkicon, + PC_stationtime, + PC_programheaders = [], + PC_showexitprogram, + } = data; + return ( +
+
{ + refocusLayout(); + }}> + + + {PC_stationtime} + + + NtOS + + +
+ {PC_programheaders.map(header => ( + + + + ))} + + {PC_ntneticon && ( + + )} + + {!!PC_showbatteryicon && PC_batteryicon && ( + + {PC_batteryicon && ( + + )} + {PC_batterypercent && ( + PC_batterypercent + )} + + )} + {PC_apclinkicon && ( + + + + )} + {!!PC_showexitprogram && ( +
+
+
+ {children} +
+
+ ); +}; diff --git a/tgui-next/packages/tgui/interfaces/PersonalCrafting.js b/tgui-next/packages/tgui/interfaces/PersonalCrafting.js index 4787f431322..c94355590ac 100644 --- a/tgui-next/packages/tgui/interfaces/PersonalCrafting.js +++ b/tgui-next/packages/tgui/interfaces/PersonalCrafting.js @@ -1,4 +1,4 @@ -import { map } from 'common/fp'; +import { map } from 'common/collections'; import { Fragment } from 'inferno'; import { act } from '../byond'; import { Box, Button, Dimmer, Icon, LabeledList, Section, Tabs } from '../components'; diff --git a/tgui-next/packages/tgui/interfaces/PowerMonitor.js b/tgui-next/packages/tgui/interfaces/PowerMonitor.js new file mode 100644 index 00000000000..07cfead616a --- /dev/null +++ b/tgui-next/packages/tgui/interfaces/PowerMonitor.js @@ -0,0 +1,223 @@ +import { map, sortBy } from 'common/collections'; +import { flow } from 'common/fp'; +import { toFixed } from 'common/math'; +import { pureComponentHooks } from 'common/react'; +import { Component, Fragment } from 'inferno'; +import { Box, Button, Chart, ColorBox, Flex, Icon, LabeledList, ProgressBar, Section, Table } from '../components'; + +const PEAK_DRAW = 500000; + +const powerRank = str => { + const unit = String(str.split(' ')[1]).toLowerCase(); + return ['w', 'kw', 'mw', 'gw'].indexOf(unit); +}; + +export class PowerMonitor extends Component { + constructor() { + super(); + this.state = { + sortByField: null, + }; + } + + render() { + const { state } = this.props; + const { config, data } = state; + const { ref } = config; + const { history } = data; + const { sortByField } = this.state; + const supply = history.supply[history.supply.length - 1] || 0; + const demand = history.demand[history.demand.length - 1] || 0; + const supplyData = history.supply.map((value, i) => [i, value]); + const demandData = history.demand.map((value, i) => [i, value]); + const maxValue = Math.max( + PEAK_DRAW, + ...history.supply, + ...history.demand); + // Process area data + const areas = flow([ + map((area, i) => ({ + ...area, + // Generate a unique id + id: area.name + i, + })), + sortByField === 'name' && sortBy(area => area.name), + sortByField === 'charge' && sortBy(area => -area.charge), + sortByField === 'draw' && sortBy( + area => -powerRank(area.load), + area => -parseFloat(area.load)), + ])(data.areas); + return ( + + + +
+ + + + + + + + +
+
+ +
+ + +
+
+
+
+ + + Sort by: + + this.setState({ + sortByField: sortByField !== 'name' && 'name', + })} /> + this.setState({ + sortByField: sortByField !== 'charge' && 'charge', + })} /> + this.setState({ + sortByField: sortByField !== 'draw' && 'draw', + })} /> + + + + + Area + + + Charge + + + Draw + + + Eqp + + + Lgt + + + Env + + + {areas.map((area, i) => ( + + + + + + + + + ))} +
+ {area.name} + + + + {area.load} + + + + + + +
+
+
+ ); + } +} + +const AreaCharge = props => { + const { charging, charge } = props; + return ( + + 50 + ? 'battery-half' + : 'battery-quarter' + ) + || charging === 1 && 'bolt' + || charging === 2 && 'battery-full' + )} + color={( + charging === 0 && ( + charge > 50 + ? 'yellow' + : 'red' + ) + || charging === 1 && 'yellow' + || charging === 2 && 'green' + )} /> + + {toFixed(charge) + '%'} + + + ); +}; + +AreaCharge.defaultHooks = pureComponentHooks; + +const AreaStatusColorBox = props => { + const { status } = props; + const power = Boolean(status & 2); + const mode = Boolean(status & 1); + const tooltipText = (power ? 'On' : 'Off') + + ` [${mode ? 'auto' : 'manual'}]`; + return ( + + ); +}; + +AreaStatusColorBox.defaultHooks = pureComponentHooks; diff --git a/tgui-next/packages/tgui/interfaces/Radio.js b/tgui-next/packages/tgui/interfaces/Radio.js index d918710c41c..0a68eef0608 100644 --- a/tgui-next/packages/tgui/interfaces/Radio.js +++ b/tgui-next/packages/tgui/interfaces/Radio.js @@ -1,4 +1,4 @@ -import { map } from 'common/fp'; +import { map } from 'common/collections'; import { toFixed } from 'common/math'; import { act } from '../byond'; import { Box, Button, LabeledList, NumberInput, Section } from '../components'; diff --git a/tgui-next/packages/tgui/interfaces/RapidPipeDispenser.js b/tgui-next/packages/tgui/interfaces/RapidPipeDispenser.js new file mode 100644 index 00000000000..fc83985f2ce --- /dev/null +++ b/tgui-next/packages/tgui/interfaces/RapidPipeDispenser.js @@ -0,0 +1,190 @@ +import { classes } from 'common/react'; +import { Fragment } from 'inferno'; +import { act } from '../byond'; +import { Box, Button, ColorBox, Flex, LabeledList, Section, Tabs } from '../components'; + +const ROOT_CATEGORIES = [ + 'Atmospherics', + 'Disposals', + 'Transit Tubes', +]; + +const ICON_BY_CATEGORY_NAME = { + 'Atmospherics': 'wrench', + 'Disposals': 'trash-alt', + 'Transit Tubes': 'bus', + 'Pipes': 'grip-lines', + 'Disposal Pipes': 'grip-lines', + 'Devices': 'microchip', + 'Heat Exchange': 'thermometer-half', + 'Station Equipment': 'microchip', +}; + +const PAINT_COLORS = { + grey: '#bbbbbb', + amethyst: '#a365ff', + blue: '#4466ff', + brown: '#b26438', + cyan: '#48eae8', + dark: '#808080', + green: '#1edd00', + orange: '#ffa030', + purple: '#b535ea', + red: '#ff3333', + violet: '#6e00f6', + yellow: '#ffce26', +}; + +const TOOLS = [ + { + name: 'Dispense', + bitmask: 1, + }, + { + name: 'Connect', + bitmask: 2, + }, + { + name: 'Destroy', + bitmask: 4, + }, + { + name: 'Paint', + bitmask: 8, + }, +]; + +export const RapidPipeDispenser = props => { + const { state } = props; + const { config, data } = state; + const { ref } = config; + const { + category: rootCategoryIndex, + categories = [], + selected_color, + piping_layer, + mode, + } = data; + const previews = data.preview_rows.flatMap(row => row.previews); + return ( + +
+ + + {ROOT_CATEGORIES.map((categoryName, i) => ( +
+ + +
+ {rootCategoryIndex === 0 && ( + + {[1, 2, 3].map(layer => ( + act(ref, 'piping_layer', { + piping_layer: layer, + })} /> + ))} + + )} + + {previews.map(preview => ( + + ))} + +
+
+ +
+ + {categories.map(category => ( + + {() => category.recipes.map(recipe => ( + act(ref, 'pipe_type', { + pipe_type: recipe.pipe_index, + category: category.cat_name, + })} /> + ))} + + ))} + +
+
+
+
+ ); +}; diff --git a/tgui-next/packages/tgui/interfaces/SMES.js b/tgui-next/packages/tgui/interfaces/SMES.js index 4d5534c9a55..98e0e86e22b 100644 --- a/tgui-next/packages/tgui/interfaces/SMES.js +++ b/tgui-next/packages/tgui/interfaces/SMES.js @@ -32,11 +32,11 @@ export const SMES = props => {
diff --git a/tgui-next/packages/tgui/interfaces/ShuttleManipulator.js b/tgui-next/packages/tgui/interfaces/ShuttleManipulator.js index ac71fbb79a6..4023b305e7f 100644 --- a/tgui-next/packages/tgui/interfaces/ShuttleManipulator.js +++ b/tgui-next/packages/tgui/interfaces/ShuttleManipulator.js @@ -1,7 +1,7 @@ +import { map } from 'common/collections'; import { Fragment } from 'inferno'; import { act } from '../byond'; import { Button, LabeledList, Section, Tabs } from '../components'; -import { map } from 'common/fp'; export const ShuttleManipulator = props => { const { state } = props; diff --git a/tgui-next/packages/tgui/interfaces/SmartVend.js b/tgui-next/packages/tgui/interfaces/SmartVend.js index 8c1e6c52359..5e6b9a4d3b9 100644 --- a/tgui-next/packages/tgui/interfaces/SmartVend.js +++ b/tgui-next/packages/tgui/interfaces/SmartVend.js @@ -1,8 +1,6 @@ - -import { Fragment } from 'inferno'; +import { map } from 'common/collections'; import { act } from '../byond'; -import { Button, Section, Table, NoticeBox } from '../components'; -import { map } from 'common/fp'; +import { Button, NoticeBox, Section, Table } from '../components'; export const SmartVend = props => { const { state } = props; @@ -60,4 +58,6 @@ export const SmartVend = props => { ))(data.contents)} )} -
); }; + + ); +}; diff --git a/tgui-next/packages/tgui/interfaces/common/reagents.js b/tgui-next/packages/tgui/interfaces/common/BeakerContents.js similarity index 100% rename from tgui-next/packages/tgui/interfaces/common/reagents.js rename to tgui-next/packages/tgui/interfaces/common/BeakerContents.js diff --git a/tgui-next/packages/tgui/interfaces/common/atmos.js b/tgui-next/packages/tgui/interfaces/common/atmos.js deleted file mode 100644 index 2797a2477aa..00000000000 --- a/tgui-next/packages/tgui/interfaces/common/atmos.js +++ /dev/null @@ -1,38 +0,0 @@ -const GASES = [ - { - id: 'o2', - label: 'O₂', - }, - { - id: 'n2', - label: 'N₂', - }, - { - id: 'co2', - label: 'CO₂', - }, - { - id: 'water_vapor', - label: 'H₂O', - }, - { - id: 'n2o', - label: 'N₂O', - }, - { - id: 'no2', - label: 'NO₂', - }, - { - id: 'bz', - label: 'BZ', - }, -]; - -export const getGasLabel = (gasId, fallbackValue) => { - const gas = GASES.find(gas => gas.id === gasId); - if (!gas) { - return fallbackValue || gasId; - } - return gas.label; -}; diff --git a/tgui-next/packages/tgui/layout.js b/tgui-next/packages/tgui/layout.js index 7d64db38a10..9a0df082312 100644 --- a/tgui-next/packages/tgui/layout.js +++ b/tgui-next/packages/tgui/layout.js @@ -6,6 +6,7 @@ import { Box, TitleBar } from './components'; import { Toast } from './components/Toast'; import { UI_INTERACTIVE } from './constants'; import { dragStartHandler, resizeStartHandler } from './drag'; +import { releaseHeldKeys } from './hotkeys'; import { createLogger } from './logging'; import { refocusLayout } from './refocus'; import { getRoute } from './routes'; @@ -25,10 +26,34 @@ export class Layout extends Component { if (!route) { return `Component for '${config.interface}' was not found.`; } - const Component = route.component(); + const RoutedComponent = route.component(); + const WrapperComponent = route.wrapper && route.wrapper(); const { scrollable } = route; + // Render content + let contentElement = ( +
+ + + +
+ ); + // Wrap into the wrapper component + if (WrapperComponent) { + contentElement = ( + + {contentElement} + + ); + } return ( - +
{ logger.log('pressed close'); + releaseHeldKeys(); winset(config.window, 'is-visible', false); runCommand(`uiclose ${config.ref}`); }} /> -
- - - -
+ {contentElement} {config.status !== UI_INTERACTIVE && (
)} @@ -66,7 +83,7 @@ export class Layout extends Component { onMousedown={resizeStartHandler(1, 1)} /> )} - +
); } } diff --git a/tgui-next/packages/tgui/public/tgui-fallback.html b/tgui-next/packages/tgui/public/tgui-fallback.html index a3a92607a4c..b9757bac483 100644 --- a/tgui-next/packages/tgui/public/tgui-fallback.html +++ b/tgui-next/packages/tgui/public/tgui-fallback.html @@ -32,14 +32,27 @@ var ref = location.search.replace('?ref=', ''); document.getElementById('data').setAttribute('data-ref', ref); // Early global error handling window.onerror = function (msg, url, line, col, error) { - var logString = msg + ' (' + url + ' line ' + line + ')' - + '\nUser Agent: ' + navigator.userAgent; - // IE8: Print to the page - document.write(logString); + // Proper stacktrace + var stack = error && error.stack; + // Ghetto stacktrace + if (!stack) { + stack = msg + '\n at ' + url + ':' + line; + if (col) { + stack += ':' + col; + } + } + // Append user agent info + stack += '\n\nUser Agent: ' + navigator.userAgent; + // Print error to the page + document.write(stack); // Send to the backend location.href = 'byond://?src=' + ref - + '&action=tgui:log&error=true' - + '&log=' + encodeURIComponent(logString); + + '&action=tgui:log&fatal=1' + + '&log=' + encodeURIComponent(stack); + // Short-circuit further updates + window.update = window.initialize = function () {}; + // Prevent default action + return true; }; diff --git a/tgui-next/packages/tgui/public/tgui-main.html b/tgui-next/packages/tgui/public/tgui-main.html index 4e391674396..4e02a9db7c8 100644 --- a/tgui-next/packages/tgui/public/tgui-main.html +++ b/tgui-next/packages/tgui/public/tgui-main.html @@ -11,16 +11,35 @@ window.__inception__ = new Date().getTime(); // Make window ref global window.__ref__ = document.getElementById('data').getAttribute('data-ref'); -// Early global error handling +// Global error handling window.onerror = function (msg, url, line, col, error) { - var logString = msg + ' (' + url + ' line ' + line + ')' - + '\nUser Agent: ' + navigator.userAgent; - // IE8: Print to the page - document.write(logString); + // Proper stacktrace + var stack = error && error.stack; + // Ghetto stacktrace + if (!stack) { + stack = msg + '\n at ' + url + ':' + line; + if (col) { + stack += ':' + col; + } + } + // Append user agent info + stack += '\n\nUser Agent: ' + navigator.userAgent; + // Print error to the page + var errorRoot = document.getElementById('FatalError'); + var errorStack = document.getElementById('FatalError__stack'); + if (errorRoot) { + errorRoot.className = 'FatalError'; + errorStack.textContent = stack; + } // Send to the backend location.href = 'byond://?src=' + window.__ref__ - + '&action=tgui:log&error=true' - + '&log=' + encodeURIComponent(logString); + + '&action=tgui:log&fatal=1' + + '&log=' + encodeURIComponent(stack); + // Short-circuit further updates + window.__updateQueue__ = []; + window.update = window.initialize = function () {}; + // Prevent default action + return true; }; // Early initialization window.__updateQueue__ = []; @@ -48,12 +67,50 @@ location.href = 'byond://?src=' + window.__ref__ - +
- +
+ + +