From c05536badd6d812c610954a8f837f0d808643bf0 Mon Sep 17 00:00:00 2001 From: Nichlas Pihl Date: Mon, 9 Dec 2019 17:05:46 +0000 Subject: [PATCH 01/11] https://github.com/tgstation/tgstation/pull/47972 minus the uis --- tgui-next/README.md | 26 ++++ .../packages/tgui/components/DropDown.js | 123 ++++++++++++++++++ tgui-next/packages/tgui/components/Input.js | 4 +- tgui-next/packages/tgui/components/index.js | 1 + .../tgui/styles/components/Dropdown.scss | 47 +++++++ .../tgui/styles/components/NumberInput.scss | 1 + tgui-next/packages/tgui/styles/main.scss | 1 + 7 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 tgui-next/packages/tgui/components/DropDown.js create mode 100644 tgui-next/packages/tgui/styles/components/Dropdown.scss diff --git a/tgui-next/README.md b/tgui-next/README.md index a09f9e522e02..44a62dbc881a 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. diff --git a/tgui-next/packages/tgui/components/DropDown.js b/tgui-next/packages/tgui/components/DropDown.js new file mode 100644 index 000000000000..f726268b9d47 --- /dev/null +++ b/tgui-next/packages/tgui/components/DropDown.js @@ -0,0 +1,123 @@ +import { Component, createRef } from "inferno"; +import { classes } from "common/react"; +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 = event => { + 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..4ce47d32b4e2 100644 --- a/tgui-next/packages/tgui/components/Input.js +++ b/tgui-next/packages/tgui/components/Input.js @@ -86,6 +86,7 @@ export class Input extends Component { onInput, onChange, value, + maxLength, ...boxProps } = props; // Box props @@ -111,7 +112,8 @@ export class Input extends Component { onInput={this.handleInput} onFocus={this.handleFocus} onBlur={this.handleBlur} - onKeyDown={this.handleKeyDown} /> + onKeyDown={this.handleKeyDown} + maxLength={maxLength} /> ); } diff --git a/tgui-next/packages/tgui/components/index.js b/tgui-next/packages/tgui/components/index.js index 2bbc6aaa5d0e..c0074b4eec55 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/styles/components/Dropdown.scss b/tgui-next/packages/tgui/styles/components/Dropdown.scss new file mode 100644 index 000000000000..e31332c5fdcf --- /dev/null +++ b/tgui-next/packages/tgui/styles/components/Dropdown.scss @@ -0,0 +1,47 @@ +.Dropdown { + position: relative; +} + +.Dropdown__control { + position: relative; + display: inline-block; + font-family: Verdana, sans-serif; + font-size: 12px; + width: 100px; + line-height: 17px; + user-select: none; +} + +.Dropdown__arrow-button { + float: right; + padding-left: 6px; + border-left: 1px solid #000; + border-left: 1px solid rgba(0, 0, 0, 0.25); +} + +.Dropdown__menu { + position: absolute; + overflow-y: auto; + z-index: 5; + width: 100px; + border-radius: 0 0 2px 2px; + background-color: #000; + background-color: rgba(0, 0, 0, 0.75); +} + +.Dropdown__menuentry { + padding: 2px 4px; + font-family: Verdana, sans-serif; + font-size: 12px; + line-height: 17px; + transition: background-color 100ms; + &:hover { + background-color: #444; + transition: background-color 0ms; + } +} + +.Dropdown__over { + top: auto; + bottom: 100% +} diff --git a/tgui-next/packages/tgui/styles/components/NumberInput.scss b/tgui-next/packages/tgui/styles/components/NumberInput.scss index 14f4303f92d0..35a99019cb74 100644 --- a/tgui-next/packages/tgui/styles/components/NumberInput.scss +++ b/tgui-next/packages/tgui/styles/components/NumberInput.scss @@ -19,6 +19,7 @@ $border-radius: Input.$border-radius !default; line-height: 17px; text-align: right; overflow: visible; + cursor: n-resize; } .NumberInput--fluid { diff --git a/tgui-next/packages/tgui/styles/main.scss b/tgui-next/packages/tgui/styles/main.scss index be8a8ba1356e..75b813226024 100644 --- a/tgui-next/packages/tgui/styles/main.scss +++ b/tgui-next/packages/tgui/styles/main.scss @@ -16,6 +16,7 @@ @include meta.load-css('./components/BlockQuote.scss'); @include meta.load-css('./components/Button.scss'); @include meta.load-css('./components/ColorBox.scss'); +@include meta.load-css('./components/Dropdown.scss'); @include meta.load-css('./components/FatalError.scss'); @include meta.load-css('./components/Flex.scss'); @include meta.load-css('./components/Input.scss'); From 734a4e9864593155a80e37c487dfa371917b8dbf Mon Sep 17 00:00:00 2001 From: Nichlas Pihl Date: Mon, 9 Dec 2019 17:10:45 +0000 Subject: [PATCH 02/11] https://github.com/tgstation/tgstation/pull/47983/files minus the chem uis --- tgui-next/README.md | 1 + tgui-next/packages/tgui/components/Input.js | 17 +++++++++++++---- .../packages/tgui/styles/components/Input.scss | 6 ++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/tgui-next/README.md b/tgui-next/README.md index 44a62dbc881a..b8520392e0d6 100644 --- a/tgui-next/README.md +++ b/tgui-next/README.md @@ -514,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/packages/tgui/components/Input.js b/tgui-next/packages/tgui/components/Input.js index 4ce47d32b4e2..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); } } @@ -87,6 +94,7 @@ export class Input extends Component { onChange, value, maxLength, + placeholder, ...boxProps } = props; // Box props @@ -109,6 +117,7 @@ export class Input extends Component { Date: Sun, 1 Dec 2019 19:33:34 -0600 Subject: [PATCH 03/11] scanner gate tgui-next update (#48029) * scanner gate tgui-next update * reworked wording and species naming * updated spacing * better buttons, better define usage * Migrate to useBackend, do lots of cosmetic fixes --- code/game/machinery/scan_gate.dm | 135 +++---- .../packages/tgui/interfaces/ScannerGate.js | 351 ++++++++++++++++++ tgui-next/packages/tgui/routes.js | 5 + 3 files changed, 425 insertions(+), 66 deletions(-) create mode 100644 tgui-next/packages/tgui/interfaces/ScannerGate.js 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/tgui-next/packages/tgui/interfaces/ScannerGate.js b/tgui-next/packages/tgui/interfaces/ScannerGate.js new file mode 100644 index 000000000000..f1e82729bc54 --- /dev/null +++ b/tgui-next/packages/tgui/interfaces/ScannerGate.js @@ -0,0 +1,351 @@ +import { Fragment } from 'inferno'; +import { useBackend } from '../backend'; +import { Box, Button, LabeledList, NumberInput, Section } from '../components'; +import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; + +const DISEASE_THEASHOLD_LIST = [ + 'Positive', + 'Harmless', + 'Minor', + 'Medium', + 'Harmful', + 'Dangerous', + 'BIOHAZARD', +]; + +const TARGET_SPECIES_LIST = [ + { + name: 'Human', + value: 'human', + }, + { + name: 'Lizardperson', + value: 'lizard', + }, + { + name: 'Flyperson', + value: 'fly', + }, + { + name: 'Felinid', + value: 'felinid', + }, + { + name: 'Plasmaman', + value: 'plasma', + }, + { + name: 'Mothperson', + value: 'moth', + }, + { + name: 'Jellyperson', + value: 'jelly', + }, + { + name: 'Podperson', + value: 'pod', + }, + { + name: 'Golem', + value: 'golem', + }, + { + name: 'Zombie', + value: 'zombie', + }, +]; + +const TARGET_NUTRITION_LIST = [ + { + name: 'Starving', + value: 150, + }, + { + name: 'Obese', + value: 600, + }, +]; + +export const ScannerGate = props => { + const { state } = props; + const { act, data } = useBackend(props); + return ( + + act('toggle_lock')} /> + {!data.locked && ( + + )} + + ); +}; + +const SCANNER_GATE_ROUTES = { + Off: { + title: 'Scanner Mode: Off', + component: () => ScannerGateOff, + }, + Wanted: { + title: 'Scanner Mode: Wanted', + component: () => ScannerGateWanted, + }, + Guns: { + title: 'Scanner Mode: Guns', + component: () => ScannerGateGuns, + }, + Mindshield: { + title: 'Scanner Mode: Mindshield', + component: () => ScannerGateMindshield, + }, + Disease: { + title: 'Scanner Mode: Disease', + component: () => ScannerGateDisease, + }, + Species: { + title: 'Scanner Mode: Species', + component: () => ScannerGateSpecies, + }, + Nutrition: { + title: 'Scanner Mode: Nutrition', + component: () => ScannerGateNutrition, + }, + Nanites: { + title: 'Scanner Mode: Nanites', + component: () => ScannerGateNanites, + }, +}; + +const ScannerGateControl = props => { + const { state } = props; + const { act, data } = useBackend(props); + const { scan_mode } = data; + const route = SCANNER_GATE_ROUTES[scan_mode] + || SCANNER_GATE_ROUTES.off; + const Component = route.component(); + return ( +
act('set_mode', { new_mode: 'Off' })} /> + )}> + +
+ ); +}; + +const ScannerGateOff = props => { + const { act } = useBackend(props); + return ( + + + Select a scanning mode below. + + +