diff --git a/code/game/machinery/scan_gate.dm b/code/game/machinery/scan_gate.dm index 0ed85b1a3673..fcae3dee926f 100644 --- a/code/game/machinery/scan_gate.dm +++ b/code/game/machinery/scan_gate.dm @@ -7,6 +7,17 @@ #define SCANGATE_SPECIES "Species" #define SCANGATE_NUTRITION "Nutrition" +#define SCANGATE_HUMAN "human" +#define SCANGATE_LIZARD "lizard" +#define SCANGATE_FELINID "felinid" +#define SCANGATE_FLY "fly" +#define SCANGATE_PLASMAMAN "plasma" +#define SCANGATE_MOTH "moth" +#define SCANGATE_JELLY "jelly" +#define SCANGATE_POD "pod" +#define SCANGATE_GOLEM "golem" +#define SCANGATE_ZOMBIE "zombie" + /obj/machinery/scanner_gate name = "scanner gate" desc = "A gate able to perform mid-depth scans on any organisms who pass under it." @@ -21,8 +32,8 @@ var/locked = FALSE var/scangate_mode = SCANGATE_NONE var/disease_threshold = DISEASE_SEVERITY_MINOR - var/nanite_cloud = 0 - var/datum/species/detect_species = /datum/species/human + var/nanite_cloud = 1 + var/detect_species = SCANGATE_HUMAN var/reverse = FALSE //If true, signals if the scan returns false var/detect_nutrition = NUTRITION_LEVEL_FAT @@ -106,9 +117,29 @@ if(SCANGATE_SPECIES) if(ishuman(M)) var/mob/living/carbon/human/H = M - if(is_species(H, detect_species)) + var/datum/species/scan_species = /datum/species/human + switch(detect_species) + if(SCANGATE_LIZARD) + scan_species = /datum/species/lizard + if(SCANGATE_FLY) + scan_species = /datum/species/fly + if(SCANGATE_FELINID) + scan_species = /datum/species/human/felinid + if(SCANGATE_PLASMAMAN) + scan_species = /datum/species/plasmaman + if(SCANGATE_MOTH) + scan_species = /datum/species/moth + if(SCANGATE_JELLY) + scan_species = /datum/species/jelly + if(SCANGATE_POD) + scan_species = /datum/species/pod + if(SCANGATE_GOLEM) + scan_species = /datum/species/golem + if(SCANGATE_ZOMBIE) + scan_species = /datum/species/zombie + if(is_species(H, scan_species)) beep = TRUE - if(detect_species == /datum/species/zombie) //Can detect dormant zombies + if(detect_species == SCANGATE_ZOMBIE) //Can detect dormant zombies if(H.getorganslot(ORGAN_SLOT_ZOMBIE)) beep = TRUE if(SCANGATE_GUNS) @@ -144,19 +175,21 @@ return FALSE return ..() -/obj/machinery/scanner_gate/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) - SStgui.try_update_ui(user, src, ui_key, ui, force_open) +/obj/machinery/scanner_gate/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + 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, "scanner_gate", name, 600, 400, master_ui, state) ui.open() /obj/machinery/scanner_gate/ui_data() var/list/data = list() + data["locked"] = locked data["scan_mode"] = scangate_mode data["reverse"] = reverse data["nanite_cloud"] = nanite_cloud data["disease_threshold"] = disease_threshold - data["target_species"] = initial(detect_species.name) + data["target_species"] = detect_species data["target_nutrition"] = detect_nutrition return data @@ -165,77 +198,36 @@ return switch(action) if("set_mode") - var/new_mode = input("Choose the scan mode","Scan Mode") as null|anything in list(SCANGATE_NONE, - SCANGATE_MINDSHIELD, - SCANGATE_NANITES, - SCANGATE_DISEASE, - SCANGATE_GUNS, - SCANGATE_WANTED, - SCANGATE_SPECIES, - SCANGATE_NUTRITION) - if(new_mode) - scangate_mode = new_mode + var/new_mode = params["new_mode"] + scangate_mode = new_mode . = TRUE if("toggle_reverse") reverse = !reverse . = TRUE + if("toggle_lock") + if(allowed(usr)) + locked = !locked + . = TRUE if("set_disease_threshold") - var/new_threshold = input("Set disease threshold","Scan Mode") as null|anything in list(DISEASE_SEVERITY_POSITIVE, - DISEASE_SEVERITY_NONTHREAT, - DISEASE_SEVERITY_MINOR, - DISEASE_SEVERITY_MEDIUM, - DISEASE_SEVERITY_HARMFUL, - DISEASE_SEVERITY_DANGEROUS, - DISEASE_SEVERITY_BIOHAZARD) - if(new_threshold) - disease_threshold = new_threshold + var/new_threshold = params["new_threshold"] + disease_threshold = new_threshold . = TRUE if("set_nanite_cloud") - var/new_cloud = input("Set target nanite cloud","Scan Mode", nanite_cloud) as null|num - if(!isnull(new_cloud)) - nanite_cloud = CLAMP(round(new_cloud, 1), 1, 100) + var/new_cloud = text2num(params["new_cloud"]) + nanite_cloud = CLAMP(round(new_cloud, 1), 1, 100) . = TRUE //Some species are not scannable, like abductors (too unknown), androids (too artificial) or skeletons (too magic) if("set_target_species") - var/new_species = input("Set target species","Scan Mode") as null|anything in list("Human", - "Lizardperson", - "Gorillaperson", // yogs -- gorilla people - "Flyperson", - "Plasmaman", - "Mothmen", - "Jellyperson", - "Podperson", - "Golem", - "Zombie") - if(new_species) - switch(new_species) - if("Human") - detect_species = /datum/species/human - if("Lizardperson") - detect_species = /datum/species/lizard - // yogs start -- gorilla people - if("Gorillaperson") - detect_species = /datum/species/gorilla - // yogs end - if("Flyperson") - detect_species = /datum/species/fly - if("Plasmaman") - detect_species = /datum/species/plasmaman - if("Mothmen") - detect_species = /datum/species/moth - if("Jellyperson") - detect_species = /datum/species/jelly - if("Podperson") - detect_species = /datum/species/pod - if("Golem") - detect_species = /datum/species/golem - if("Zombie") - detect_species = /datum/species/zombie + var/new_species = params["new_species"] + detect_species = new_species . = TRUE if("set_target_nutrition") - var/new_nutrition = input("Set target nutrition level","Scan Mode") as null|anything in list("Starving", - "Obese") - if(new_nutrition) + var/new_nutrition = params["new_nutrition"] + var/nutrition_list = list( + "Starving", + "Obese" + ) + if(new_nutrition && new_nutrition in nutrition_list) switch(new_nutrition) if("Starving") detect_nutrition = NUTRITION_LEVEL_STARVING @@ -251,3 +243,14 @@ #undef SCANGATE_WANTED #undef SCANGATE_SPECIES #undef SCANGATE_NUTRITION + +#undef SCANGATE_HUMAN +#undef SCANGATE_LIZARD +#undef SCANGATE_FELINID +#undef SCANGATE_FLY +#undef SCANGATE_PLASMAMAN +#undef SCANGATE_MOTH +#undef SCANGATE_JELLY +#undef SCANGATE_POD +#undef SCANGATE_GOLEM +#undef SCANGATE_ZOMBIE diff --git a/code/modules/power/gravitygenerator.dm b/code/modules/power/gravitygenerator.dm index 594f7bb23688..b21f37c3bdad 100644 --- a/code/modules/power/gravitygenerator.dm +++ b/code/modules/power/gravitygenerator.dm @@ -220,40 +220,33 @@ GLOBAL_LIST_EMPTY(gravity_generators) // We will keep track of this by adding ne return return ..() -/obj/machinery/gravity_generator/main/ui_interact(mob/user) - if(stat & BROKEN) - return - var/dat = "Gravity Generator Breaker: " - if(breaker) - dat += "ON OFF" - else - dat += "ON OFF " - - dat += "
Generator Status:
" - if(charging_state != POWER_IDLE) - dat += "WARNING Radiation Detected.
[charging_state == POWER_UP ? "Charging..." : "Discharging..."]" - else if(on) - dat += "Powered." - else - dat += "Unpowered." - - dat += "
Gravity Charge: [charge_count]%
" +/obj/machinery/gravity_generator/main/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + 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, "gravity_generator", name, 400, 200, master_ui, state) + ui.open() - var/datum/browser/popup = new(user, "gravgen", name) - popup.set_content(dat) - popup.open() +/obj/machinery/gravity_generator/main/ui_data(mob/user) + var/list/data = list() + data["breaker"] = breaker + data["charge_count"] = charge_count + data["charging_state"] = charging_state + data["on"] = on + data["operational"] = (stat & BROKEN) ? FALSE : TRUE + return data -/obj/machinery/gravity_generator/main/Topic(href, href_list) +/obj/machinery/gravity_generator/main/ui_act(action, params) if(..()) return - if(href_list["gentoggle"]) - breaker = !breaker - investigate_log("was toggled [breaker ? "ON" : "OFF"] by [key_name(usr)].", INVESTIGATE_GRAVITY) - set_power() - src.updateUsrDialog() + switch(action) + if("gentoggle") + breaker = !breaker + investigate_log("was toggled [breaker ? "ON" : "OFF"] by [key_name(usr)].", INVESTIGATE_GRAVITY) + set_power() // Power and Icon States diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm index 5529069f7764..fc274dff4e07 100644 --- a/code/modules/reagents/chemistry/machinery/chem_master.dm +++ b/code/modules/reagents/chemistry/machinery/chem_master.dm @@ -270,7 +270,7 @@ "Maximum [vol_each_max] units per item.", "How many units to fill?", vol_each_max)) - vol_each = CLAMP(round(vol_each), 0, vol_each_max) + vol_each = CLAMP(vol_each, 0, vol_each_max) if(vol_each <= 0) return FALSE // Get item name diff --git a/code/modules/reagents/chemistry/machinery/smoke_machine.dm b/code/modules/reagents/chemistry/machinery/smoke_machine.dm index 259921f43dda..46e8fa081ca9 100644 --- a/code/modules/reagents/chemistry/machinery/smoke_machine.dm +++ b/code/modules/reagents/chemistry/machinery/smoke_machine.dm @@ -104,7 +104,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, "smoke_machine", name, 450, 350, master_ui, state) + ui = new(user, src, ui_key, "smoke_machine", name, 350, 350, master_ui, state) ui.open() /obj/machinery/smoke_machine/ui_data(mob/user) diff --git a/tgui-next/.eslintrc-harder.yml b/tgui-next/.eslintrc-harder.yml new file mode 100644 index 000000000000..eb06f5b33db9 --- /dev/null +++ b/tgui-next/.eslintrc-harder.yml @@ -0,0 +1,14 @@ +rules: + ## Enforce a maximum cyclomatic complexity allowed in a program + complexity: [error, { max: 25 }] + ## Enforce consistent brace style for blocks + brace-style: [error, stroustrup, { allowSingleLine: false }] + ## Enforce the consistent use of either backticks, double, or single quotes + quotes: [error, single, { + avoidEscape: true, + allowTemplateLiterals: true, + }] + react/jsx-closing-bracket-location: [error, { + selfClosing: after-props, + nonEmpty: after-props, + }] diff --git a/tgui-next/.eslintrc.yml b/tgui-next/.eslintrc.yml index f67ab2c9a781..4bff333295d7 100644 --- a/tgui-next/.eslintrc.yml +++ b/tgui-next/.eslintrc.yml @@ -18,7 +18,8 @@ rules: ## Possible Errors ## ---------------------------------------- - ## Enforce “for” loop update clause moving the counter in the right direction. + ## Enforce “for” loop update clause moving the counter in the right + ## direction. # for-direction: error ## Enforce return statements in getters # getter-return: error @@ -66,7 +67,8 @@ rules: no-invalid-regexp: error ## Disallow irregular whitespace no-irregular-whitespace: error - ## Disallow characters which are made with multiple code points in character class syntax + ## Disallow characters which are made with multiple code points in character + ## class syntax no-misleading-character-class: error ## Disallow calling global object properties as functions no-obj-calls: error @@ -80,13 +82,15 @@ rules: no-template-curly-in-string: error ## Disallow confusing multiline expressions no-unexpected-multiline: error - ## Disallow unreachable code after return, throw, continue, and break statements + ## Disallow unreachable code after return, throw, continue, and break + ## statements # no-unreachable: warn ## Disallow control flow statements in finally blocks no-unsafe-finally: error ## Disallow negating the left operand of relational operators no-unsafe-negation: error - ## Disallow assignments that can lead to race conditions due to usage of await or yield + ## Disallow assignments that can lead to race conditions due to usage of + ## await or yield # require-atomic-updates: error ## Require calls to isNaN() when checking for NaN use-isnan: error @@ -128,8 +132,9 @@ rules: ## Disallow the use of arguments.caller or arguments.callee # no-caller: error ## Disallow lexical declarations in case clauses - # no-case-declarations: error - ## Disallow division operators explicitly at the beginning of regular expressions + no-case-declarations: error + ## Disallow division operators explicitly at the beginning of regular + ## expressions # no-div-regex: error ## Disallow else blocks after return statements in if statements # no-else-return: error @@ -148,11 +153,11 @@ rules: ## Disallow unnecessary labels # no-extra-label: error ## Disallow fallthrough of case statements - # no-fallthrough: error + no-fallthrough: error ## Disallow leading or trailing decimal points in numeric literals # no-floating-decimal: error ## Disallow assignments to native objects or read-only global variables - # no-global-assign: error + no-global-assign: error ## Disallow shorthand type conversions # no-implicit-coercion: error ## Disallow variable and function declarations in the global scope @@ -167,7 +172,8 @@ rules: # no-labels: error ## Disallow unnecessary nested blocks # no-lone-blocks: error - ## Disallow function declarations that contain unsafe references inside loop statements + ## Disallow function declarations that contain unsafe references inside + ## loop statements # no-loop-func: error ## Disallow magic numbers # no-magic-numbers: error @@ -190,7 +196,7 @@ rules: ## Disallow the use of the __proto__ property # no-proto: error ## Disallow variable redeclaration - # no-redeclare: error + no-redeclare: error ## Disallow certain properties on certain objects # no-restricted-properties: error ## Disallow assignment operators in return statements @@ -233,7 +239,8 @@ rules: # prefer-named-capture-group: error ## Require using Error objects as Promise rejection reasons # prefer-promise-reject-errors: error - ## Disallow use of the RegExp constructor in favor of regular expression literals + ## Disallow use of the RegExp constructor in favor of regular expression + ## literals # prefer-regex-literals: error ## Enforce the consistent use of the radix argument when using parseInt() radix: error @@ -288,7 +295,8 @@ rules: array-bracket-spacing: [error, never] ## Enforce line breaks after each array element # array-element-newline: error - ## Disallow or enforce spaces inside of blocks after opening block and before closing block + ## Disallow or enforce spaces inside of blocks after opening block and + ## before closing block block-spacing: [error, always] ## Enforce consistent brace style for blocks # brace-style: [error, stroustrup, { allowSingleLine: false }] @@ -308,9 +316,11 @@ rules: # consistent-this: error ## Require or disallow newline at the end of files # eol-last: error - ## Require or disallow spacing between function identifiers and their invocations + ## Require or disallow spacing between function identifiers and their + ## invocations func-call-spacing: [error, never] - ## Require function names to match the name of the variable or property to which they are assigned + ## Require function names to match the name of the variable or property + ## to which they are assigned # func-name-matching: error ## Require or disallow named function expressions # func-names: error @@ -331,9 +341,11 @@ rules: # implicit-arrow-linebreak: error ## Enforce consistent indentation indent: [error, 2, { SwitchCase: 1 }] - ## Enforce the consistent use of either double or single quotes in JSX attributes + ## Enforce the consistent use of either double or single quotes in JSX + ## attributes jsx-quotes: [error, prefer-double] - ## Enforce consistent spacing between keys and values in object literal properties + ## Enforce consistent spacing between keys and values in object literal + ## properties key-spacing: [error, { beforeColon: false, afterColon: true }] ## Enforce consistent spacing before and after keywords keyword-spacing: [error, { before: true, after: true }] @@ -348,7 +360,13 @@ rules: ## Enforce a maximum depth that blocks can be nested # max-depth: error ## Enforce a maximum line length - max-len: [error, { code: 120 }] + max-len: [error, { + code: 80, + ## Ignore imports + ignorePattern: '^(import\s.+\sfrom\s|.*require\()', + ignoreUrls: true, + ignoreRegExpLiterals: true, + }] ## Enforce a maximum number of lines per file # max-lines: error ## Enforce a maximum number of line of code in a function @@ -367,7 +385,8 @@ rules: multiline-ternary: [error, always-multiline] ## Require constructor names to begin with a capital letter # new-cap: error - ## Enforce or disallow parentheses when invoking a constructor with no arguments + ## Enforce or disallow parentheses when invoking a constructor with no + ## arguments # new-parens: error ## Require a newline after each call in a method chain # newline-per-chained-call: error @@ -414,12 +433,13 @@ rules: ## Enforce the location of single-line statements # nonblock-statement-body-position: error ## Enforce consistent line breaks inside braces - # object-curly-newline: error + # object-curly-newline: [error, { multiline: true }] ## Enforce consistent spacing inside braces - # object-curly-spacing: error + object-curly-spacing: [error, always] ## Enforce placing object properties on separate lines # object-property-newline: error - ## Enforce variables to be declared either together or separately in functions + ## Enforce variables to be declared either together or separately in + ## functions # one-var: error ## Require or disallow newlines around variable declarations # one-var-declaration-per-line: error @@ -431,7 +451,8 @@ rules: # padded-blocks: error ## Require or disallow padding lines between statements # padding-line-between-statements: error - ## Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead. + ## Disallow using Object.assign with an object literal as the first + ## argument and prefer the use of object spread instead. # prefer-object-spread: error ## Require quotes around object literal property names # quote-props: error @@ -504,19 +525,23 @@ rules: # no-useless-computed-key: error ## Disallow unnecessary constructors # no-useless-constructor: error - ## Disallow renaming import, export, and destructured assignments to the same name + ## Disallow renaming import, export, and destructured assignments to the + ## same name # no-useless-rename: error ## Require let or const instead of var no-var: error - ## Require or disallow method and property shorthand syntax for object literals + ## Require or disallow method and property shorthand syntax for object + ## literals # object-shorthand: error ## Require using arrow functions for callbacks prefer-arrow-callback: error - ## Require const declarations for variables that are never reassigned after declared + ## Require const declarations for variables that are never reassigned after + ## declared # prefer-const: error ## Require destructuring from arrays and/or objects # prefer-destructuring: error - ## Disallow parseInt() and Number.parseInt() in favor of binary, octal, and hexadecimal literals + ## Disallow parseInt() and Number.parseInt() in favor of binary, octal, and + ## hexadecimal literals # prefer-numeric-literals: error ## Require rest parameters instead of arguments # prefer-rest-params: error @@ -532,7 +557,8 @@ rules: # sort-imports: error ## Require symbol descriptions # symbol-description: error - ## Require or disallow spacing around embedded expressions of template strings + ## Require or disallow spacing around embedded expressions of template + ## strings # template-curly-spacing: error ## Require or disallow spacing around the * in yield* expressions yield-star-spacing: [error, { before: false, after: true }] @@ -569,11 +595,11 @@ rules: react/no-danger: error ## Prevent problem with children and props.dangerouslySetInnerHTML react/no-danger-with-children: error - ## Prevent usage of deprecated methods, including component lifecycle methods + ## Prevent usage of deprecated methods, including component lifecycle + ## methods react/no-deprecated: error ## Prevent usage of setState in componentDidMount react/no-did-mount-set-state: error - ## <<<<< ## Prevent usage of setState in componentDidUpdate react/no-did-update-set-state: error ## Prevent direct mutation of this.state @@ -618,7 +644,8 @@ rules: # react/prop-types: error ## Prevent missing React when using JSX # react/react-in-jsx-scope: error - ## Enforce a defaultProps definition for every prop that is not a required prop + ## Enforce a defaultProps definition for every prop that is not a required + ## prop # react/require-default-props: error ## Enforce React components to have a shouldComponentUpdate method # react/require-optimization: error @@ -630,7 +657,8 @@ rules: # react/sort-comp: error ## Enforce propTypes declarations alphabetical sorting # react/sort-prop-types: error - ## Enforce the state initialization style to be either in a constructor or with a class property + ## Enforce the state initialization style to be either in a constructor or + ## with a class property react/state-in-constructor: error ## Enforces where React component static properties should be positioned. # react/static-property-placement: error @@ -643,19 +671,22 @@ rules: ## ---------------------------------------- ## Enforce boolean attributes notation in JSX (fixable) react/jsx-boolean-value: error - ## Enforce or disallow spaces inside of curly braces in JSX attributes and expressions. + ## Enforce or disallow spaces inside of curly braces in JSX attributes and + ## expressions. # react/jsx-child-element-spacing: error ## Validate closing bracket location in JSX (fixable) - ## NOTE: Too fucking annoying because all styles are viable - # react/jsx-closing-bracket-location: [error, { - # selfClosing: after-props, - # nonEmpty: after-props, - # }] + react/jsx-closing-bracket-location: [error, { + ## NOTE: Not really sure about enforcing this one + selfClosing: false, + nonEmpty: after-props, + }] ## Validate closing tag location in JSX (fixable) react/jsx-closing-tag-location: error - ## Enforce or disallow newlines inside of curly braces in JSX attributes and expressions (fixable) + ## Enforce or disallow newlines inside of curly braces in JSX attributes and + ## expressions (fixable) react/jsx-curly-newline: error - ## Enforce or disallow spaces inside of curly braces in JSX attributes and expressions (fixable) + ## Enforce or disallow spaces inside of curly braces in JSX attributes and + ## expressions (fixable) react/jsx-curly-spacing: error ## Enforce or disallow spaces around equal signs in JSX attributes (fixable) react/jsx-equals-spacing: error @@ -666,7 +697,9 @@ rules: ## Enforce event handler naming conventions in JSX react/jsx-handler-names: error ## Validate JSX indentation (fixable) - react/jsx-indent: [error, 2] + react/jsx-indent: [error, 2, { + checkAttributes: true, + }] ## Validate props indentation in JSX (fixable) react/jsx-indent-props: [error, 2] ## Validate JSX has key prop when in array or iterator @@ -705,7 +738,8 @@ rules: # react/jsx-sort-default-props: error ## Enforce props alphabetical sorting (fixable) # react/jsx-sort-props: error - ## Validate whitespace in and around the JSX opening and closing brackets (fixable) + ## Validate whitespace in and around the JSX opening and closing brackets + ## (fixable) react/jsx-tag-spacing: error ## Prevent React to be incorrectly marked as unused react/jsx-uses-react: error diff --git a/tgui-next/README.md b/tgui-next/README.md index a09f9e522e02..b8520392e0d6 100644 --- a/tgui-next/README.md +++ b/tgui-next/README.md @@ -312,6 +312,17 @@ Props: - See inherited props: [Button](#button) - `checked: boolean` - Boolean value, which marks the checkbox as checked. +### `Collapsible` + +Displays contents when open, acts as a fluid button when closed. Click to toggle, closed by default. + +Props: + - See inherited props: [Box](#box) + - `children: any` - What is collapsed when closed + - `title: string` - Text to display on the button for collapsing + - `color: string` - Color of the button; see [Button](#button) + - `buttons: any` - Buttons or other content to render inline with the button + ### `ColorBox` Displays a 1-character wide colored square. Can be used as a status indicator, @@ -333,6 +344,21 @@ Props: - See inherited props: [Box](#box) +### `Dropdown` + +A simple dropdown box component. Lets the user select from a list of options and displays selected entry. + +Props: + + - See inherited props: [Box](#box) + - `options: string[]` - An array of strings which will be displayed in the dropdown when open + - `selected: string` - Currently selected entry + - `width: number` - Width of dropdown button and resulting menu + - `over: boolean` - dropdown renders over instead of below + - `color: string` - color of dropdown button + - `onClick: (e) => void` - Called when dropdown button is clicked + - `onSet: (e, value) => void` - Called when a value is picked from the list, `value` is the value that was picked + ### `Flex` Quickly manage the layout, alignment, and sizing of grid columns, navigation, components, and more with a full suite of responsive flexbox utilities. @@ -488,6 +514,7 @@ Props: - See inherited props: [Box](#box) - `value: string` - Value of an input. +- `placeholder: string` - Text placed into Input box when value is otherwise nothing. Clears automatically when focused. - `fluid: boolean` - Fill all available horizontal space. - `onChange: (e, value) => void` - An event, which fires when you commit the text by either unfocusing the input box, or by pressing the Enter key. diff --git a/tgui-next/bin/tgui b/tgui-next/bin/tgui index c13f6aa2de46..401d67b62270 100755 --- a/tgui-next/bin/tgui +++ b/tgui-next/bin/tgui @@ -132,6 +132,13 @@ if [[ ${1} == '--lint' ]]; then exit 0 fi +if [[ ${1} == '--lint-harder' ]]; then + shift 1 + task-install + task-eslint -c .eslintrc-harder.yml "${@}" + exit 0 +fi + ## Analyze the bundle if [[ ${1} == '--analyze' ]]; then task-install diff --git a/tgui-next/packages/common/string.js b/tgui-next/packages/common/string.js index 8d4100945ca7..10680a460f77 100644 --- a/tgui-next/packages/common/string.js +++ b/tgui-next/packages/common/string.js @@ -64,8 +64,8 @@ export const toTitleCase = str => { // Handle string const WORDS_UPPER = ['Id', 'Tv']; const WORDS_LOWER = [ - 'A', 'An', 'And', 'As', 'At', 'But', 'By', 'For', 'For', 'From', 'In', 'Into', - 'Near', 'Nor', 'Of', 'On', 'Onto', 'Or', 'The', 'To', 'With', + 'A', 'An', 'And', 'As', 'At', 'But', 'By', 'For', 'For', 'From', 'In', + 'Into', 'Near', 'Nor', 'Of', 'On', 'Onto', 'Or', 'The', 'To', 'With', ]; let currentStr = str.replace(/([^\W_]+[^\s-]*) */g, str => { return str.charAt(0).toUpperCase() + str.substr(1).toLowerCase(); diff --git a/tgui-next/packages/tgui-dev-server/link/retrace.js b/tgui-next/packages/tgui-dev-server/link/retrace.js index 8ab06dae40cb..74c87e6a55fe 100644 --- a/tgui-next/packages/tgui-dev-server/link/retrace.js +++ b/tgui-next/packages/tgui-dev-server/link/retrace.js @@ -14,7 +14,7 @@ export const loadSourceMaps = async bundleDir => { // Destroy and garbage collect consumers while (sourceMaps.length !== 0) { const { consumer } = sourceMaps.shift(); - consumer.destroy(consumer); + consumer.destroy(); } // Load new sourcemaps const paths = await resolveGlob(bundleDir, '*.map'); diff --git a/tgui-next/packages/tgui-dev-server/link/server.js b/tgui-next/packages/tgui-dev-server/link/server.js index 154d3d2c771e..8f8dbdf9b5fe 100644 --- a/tgui-next/packages/tgui-dev-server/link/server.js +++ b/tgui-next/packages/tgui-dev-server/link/server.js @@ -68,7 +68,6 @@ const handleLinkMessage = msg => { // WebSocket-based client link const setupWebSocketLink = () => { - const logger = createLogger('link'); const port = 3000; const wss = new WebSocket.Server({ port }); @@ -91,7 +90,6 @@ const setupWebSocketLink = () => { // One way HTTP-based client link for IE8 const setupHttpLink = () => { - const logger = createLogger('link'); const port = 3001; const server = http.createServer((req, res) => { diff --git a/tgui-next/packages/tgui/components/Button.js b/tgui-next/packages/tgui/components/Button.js index 36aa3ca69b14..031098efe132 100644 --- a/tgui-next/packages/tgui/components/Button.js +++ b/tgui-next/packages/tgui/components/Button.js @@ -29,10 +29,11 @@ export const Button = props => { const hasContent = !!(content || children); // A warning about the lowercase onclick if (onclick) { - logger.warn("Lowercase 'onclick' is not supported on Button and " - + "lowercase prop names are discouraged in general. " - + "Please use a camelCase 'onClick' instead and read: " - + "https://infernojs.org/docs/guides/event-handling"); + logger.warn( + `Lowercase 'onclick' is not supported on Button and lowercase` + + ` prop names are discouraged in general. Please use a camelCase` + + `'onClick' instead and read: ` + + `https://infernojs.org/docs/guides/event-handling`); } // IE8: Use a lowercase "onclick" because synthetic events are fucked. // IE8: Use an "unselectable" prop because "user-select" doesn't work. diff --git a/tgui-next/packages/tgui/components/Collapsible.js b/tgui-next/packages/tgui/components/Collapsible.js index 868a2511a943..84af070fe183 100644 --- a/tgui-next/packages/tgui/components/Collapsible.js +++ b/tgui-next/packages/tgui/components/Collapsible.js @@ -16,7 +16,7 @@ export class Collapsible extends Component { const { open } = this.state; const { children, - color = "default", + color = 'default', title, buttons, ...rest diff --git a/tgui-next/packages/tgui/components/DropDown.js b/tgui-next/packages/tgui/components/DropDown.js new file mode 100644 index 000000000000..8dd3017829b9 --- /dev/null +++ b/tgui-next/packages/tgui/components/DropDown.js @@ -0,0 +1,111 @@ +import { classes } from 'common/react'; +import { Component } from 'inferno'; +import { Box } from './Box'; +import { Icon } from './Icon'; + +export class Dropdown extends Component { + constructor(props) { + super(props); + this.state = { + selected: props.selected, + open: false, + }; + this.handleClick = () => { + if (this.state.open) { + this.setOpen(false); + } + }; + } + + componentWillUnmount() { + window.removeEventListener('click', this.handleClick); + } + + setOpen(open) { + this.setState({ open: open }); + if (open) { + setTimeout(() => window.addEventListener('click', this.handleClick)); + } + else { + window.removeEventListener('click', this.handleClick); + } + } + + setSelected(selected) { + this.setState({ + selected: selected, + }); + this.setOpen(false); + this.props.onSelected(selected); + } + + buildMenu() { + const { options = [] } = this.props; + const ops = options.map(option => ( +
{ + this.setSelected(option); + }}> + {option} +
+ )); + return ops.length ? ops : 'No Options Found'; + } + + render() { + const { props } = this; + const { + color = 'default', + over, + width, + onClick, + onSet, + selected, + ...boxProps + } = props; + const { + className, + ...rest + } = boxProps; + + const adjustedOpen = over ? !this.state.open : this.state.open; + + const menu = this.state.open ? ( + + {this.buildMenu()} + + ) : null; + + return ( +
+ { + this.setOpen(!this.state.open); + }}> + + {this.state.selected} + + + + + + {menu} +
+ ); + } +} diff --git a/tgui-next/packages/tgui/components/Input.js b/tgui-next/packages/tgui/components/Input.js index 12e5f09a61ab..f2d3735de25b 100644 --- a/tgui-next/packages/tgui/components/Input.js +++ b/tgui-next/packages/tgui/components/Input.js @@ -1,7 +1,14 @@ -import { classes } from 'common/react'; +import { classes, isFalsy } from 'common/react'; import { Component, createRef } from 'inferno'; import { Box } from './Box'; +const toInputValue = value => { + if (isFalsy(value)) { + return ''; + } + return value; +}; + export class Input extends Component { constructor() { super(); @@ -50,7 +57,7 @@ export class Input extends Component { } if (e.keyCode === 27) { this.setEditing(false); - e.target.value = this.props.value; + e.target.value = toInputValue(this.props.value); e.target.blur(); return; } @@ -61,7 +68,7 @@ export class Input extends Component { const nextValue = this.props.value; const input = this.inputRef.current; if (input) { - input.value = nextValue; + input.value = toInputValue(nextValue); } } @@ -71,7 +78,7 @@ export class Input extends Component { const nextValue = this.props.value; const input = this.inputRef.current; if (input && !editing && prevValue !== nextValue) { - input.value = nextValue; + input.value = toInputValue(nextValue); } } @@ -86,6 +93,8 @@ export class Input extends Component { onInput, onChange, value, + maxLength, + placeholder, ...boxProps } = props; // Box props @@ -108,10 +117,12 @@ export class Input extends Component { + onKeyDown={this.handleKeyDown} + maxLength={maxLength} /> ); } diff --git a/tgui-next/packages/tgui/components/Tabs.js b/tgui-next/packages/tgui/components/Tabs.js index 09fa64f8d88b..b3d02ef82006 100644 --- a/tgui-next/packages/tgui/components/Tabs.js +++ b/tgui-next/packages/tgui/components/Tabs.js @@ -1,7 +1,7 @@ import { classes, normalizeChildren } from 'common/react'; import { Component } from 'inferno'; -import { Button } from './Button'; import { Box } from './Box'; +import { Button } from './Button'; // A magic value for enforcing type safety const TAB_MAGIC_TYPE = 'Tab'; @@ -9,8 +9,9 @@ const TAB_MAGIC_TYPE = 'Tab'; const validateTabs = tabs => { for (let tab of tabs) { if (!tab.props || tab.props.__type__ !== TAB_MAGIC_TYPE) { - throw new Error(" only accepts children of type ." - + "\nThis is what we received: " + JSON.stringify(tab, null, 2)); + const json = JSON.stringify(tab, null, 2); + throw new Error(' only accepts children of type .' + + 'This is what we received: ' + json); } } }; @@ -48,7 +49,7 @@ export class Tabs extends Component { } render() { - const { state, props } = this; + const { props } = this; const { className, vertical, diff --git a/tgui-next/packages/tgui/components/index.js b/tgui-next/packages/tgui/components/index.js index 2bbc6aaa5d0e..376c4da9df3d 100644 --- a/tgui-next/packages/tgui/components/index.js +++ b/tgui-next/packages/tgui/components/index.js @@ -5,6 +5,7 @@ export { Button } from './Button'; export { ColorBox } from './ColorBox'; export { Collapsible } from './Collapsible'; export { Dimmer } from './Dimmer'; +export { Dropdown } from './DropDown'; export { Flex } from './Flex'; export { Grid } from './Grid'; export { Icon } from './Icon'; diff --git a/tgui-next/packages/tgui/drag.js b/tgui-next/packages/tgui/drag.js index 263c38725380..cfdb309d0936 100644 --- a/tgui-next/packages/tgui/drag.js +++ b/tgui-next/packages/tgui/drag.js @@ -128,7 +128,7 @@ export const resizeMoveHandler = event => { }; export const resizeEndHandler = event => { - logger.log('resize end', [dragState.currentSize.x, dragState.currentSize.y]); + logger.log('resize end', dragState.currentSize); resizeHandler(event); document.removeEventListener('mousemove', resizeMoveHandler); document.removeEventListener('mouseup', resizeEndHandler); @@ -158,7 +158,8 @@ const resizeHandler = event => { * dragState.resizeMatrix.y ), }; - winset(dragState.windowRef, 'size', - // Sane window size values - Math.max(dragState.currentSize.x, 250) + ',' + Math.max(dragState.currentSize.y, 120)); + // Sane window size values + const x = Math.max(dragState.currentSize.x, 250); + const y = Math.max(dragState.currentSize.y, 120); + winset(dragState.windowRef, 'size', x + ',' + y); }; diff --git a/tgui-next/packages/tgui/hotkeys.js b/tgui-next/packages/tgui/hotkeys.js index 3983e37e70b3..08625aa64d70 100644 --- a/tgui-next/packages/tgui/hotkeys.js +++ b/tgui-next/packages/tgui/hotkeys.js @@ -172,9 +172,9 @@ const handleHotKey = (e, eventType, dispatch) => { // stack in order for this to be a fatal error. setTimeout(() => { throw new Error( - "OOPSIE WOOPSIE!! UwU We made a fucky wucky!! A wittle" - + " fucko boingo! The code monkeys at our headquarters are" - + " working VEWY HAWD to fix this!"); + 'OOPSIE WOOPSIE!! UwU We made a fucky wucky!! A wittle' + + ' fucko boingo! The code monkeys at our headquarters are' + + ' working VEWY HAWD to fix this!'); }); } dispatch({ diff --git a/tgui-next/packages/tgui/interfaces/Achievements.js b/tgui-next/packages/tgui/interfaces/Achievements.js index c73e61fbf6fb..279d5e70c51a 100644 --- a/tgui-next/packages/tgui/interfaces/Achievements.js +++ b/tgui-next/packages/tgui/interfaces/Achievements.js @@ -1,13 +1,17 @@ import { useBackend } from '../backend'; -import { Collapsible, Section} from '../components'; +import { Collapsible, Section } from '../components'; export const Achievements = props => { const { data } = useBackend(props); return ( data["achievements"].map(achievement => ( + title={achievement.unlocked + ? "" + achievement.name + " - unlocked" + : "" + achievement.name + " - locked"} + key={achievement.name} className={achievement.unlocked + ? "color-good" + : ""} >
{achievement.desc}
diff --git a/tgui-next/packages/tgui/interfaces/AirAlarm.js b/tgui-next/packages/tgui/interfaces/AirAlarm.js index 14f1c172d727..20d808606c49 100644 --- a/tgui-next/packages/tgui/interfaces/AirAlarm.js +++ b/tgui-next/packages/tgui/interfaces/AirAlarm.js @@ -1,22 +1,21 @@ import { toFixed } from 'common/math'; import { decodeHtmlEntities } from 'common/string'; import { Fragment } from 'inferno'; -import { act } from '../byond'; +import { useBackend } from '../backend'; import { Box, Button, LabeledList, NumberInput, Section } from '../components'; import { getGasLabel } from '../constants'; import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; export const AirAlarm = props => { const { state } = props; - const { config, data } = state; - const { ref } = config; + const { act, data } = useBackend(props); const locked = data.locked && !data.siliconUser; return ( act(ref, 'lock')} /> + onLockStatusChange={() => act('lock')} /> {!locked && ( @@ -26,10 +25,9 @@ export const AirAlarm = props => { }; const AirAlarmStatus = props => { - const { state } = props; - const { config, data } = state; - const { ref } = config; - const entries = data.environment_data || []; + const { data } = useBackend(props); + const entries = (data.environment_data || []) + .filter(entry => entry.value >= 0.01); const dangerMap = { 0: { color: 'good', @@ -118,8 +116,7 @@ const AIR_ALARM_ROUTES = { const AirAlarmControl = props => { const { state } = props; - const { config, data } = state; - const { ref } = config; + const { act, config } = useBackend(props); const route = AIR_ALARM_ROUTES[config.screen] || AIR_ALARM_ROUTES.home; const Component = route.component(); return ( @@ -129,7 +126,7 @@ const AirAlarmControl = props => {