Merge pull request #2880 from PhilipArmstead/prompt-additions

Adding support for text/select options in Prompt command
This commit is contained in:
hydroflame 2022-03-17 15:38:45 -04:00 committed by GitHub
commit 88df76a868
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 151 additions and 28 deletions

@ -2152,7 +2152,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
message = argsToString([message]); message = argsToString([message]);
SnackbarEvents.emit(message, variant, duration); SnackbarEvents.emit(message, variant, duration);
}, },
prompt: function (txt: any): any { prompt: function (txt: any, options?: { type?: string; options?: string[] }): any {
updateDynamicRam("prompt", getRamCost(Player, "prompt")); updateDynamicRam("prompt", getRamCost(Player, "prompt"));
if (!isString(txt)) { if (!isString(txt)) {
txt = JSON.stringify(txt); txt = JSON.stringify(txt);
@ -2161,6 +2161,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS {
return new Promise(function (resolve) { return new Promise(function (resolve) {
PromptEvent.emit({ PromptEvent.emit({
txt: txt, txt: txt,
options,
resolve: resolve, resolve: resolve,
}); });
}); });

@ -5984,19 +5984,22 @@ export interface NS extends Singularity {
tFormat(milliseconds: number, milliPrecision?: boolean): string; tFormat(milliseconds: number, milliPrecision?: boolean): string;
/** /**
* Prompt the player with a Yes/No modal. * Prompt the player with an input modal.
* @remarks * @remarks
* RAM cost: 0 GB * RAM cost: 0 GB
* *
* Prompts the player with a dialog box with two options: Yes and No. * Prompts the player with a dialog box. If `options.type` is undefined or "boolean",
* This function will return true if the player click Yes and false if * the player is shown "Yes" and "No" prompts, which return true and false respectively.
* the player clicks No. The scripts execution is halted until the player * Passing a type of "text" will give the player a text field and a value of "select"
* selects one of the options. * will show a drop-down field. Choosing type "select" will require an array or object
* to be passed via the `options.choices` property.
* The scripts execution is halted until the player selects one of the options.
* *
* @param txt - Text to appear in the prompt dialog box. * @param txt - Text to appear in the prompt dialog box.
* @returns True if the player click Yes and false if the player clicks No. * @param options - Options to modify the prompt the player is shown.
* @returns True if the player click Yes; false if the player clicks No; or the value entered by the player.
*/ */
prompt(txt: string): Promise<boolean>; prompt(txt: string, options?: { type?: "boolean"|"text"|"select"|undefined; choices?: string[] | { [key: string]: string | number } }): Promise<boolean | string>;
/** /**
* Open up a message box. * Open up a message box.

@ -691,7 +691,7 @@ export function Root(props: IProps): React.ReactElement {
if (serverScriptIndex === -1 || savedScriptCode !== server.scripts[serverScriptIndex as number].code) { if (serverScriptIndex === -1 || savedScriptCode !== server.scripts[serverScriptIndex as number].code) {
PromptEvent.emit({ PromptEvent.emit({
txt: "Do you want to save changes to " + closingScript.fileName + "?", txt: "Do you want to save changes to " + closingScript.fileName + "?",
resolve: (result: boolean) => { resolve: (result: boolean | string) => {
if (result) { if (result) {
// Save changes // Save changes
closingScript.code = savedScriptCode; closingScript.code = savedScriptCode;
@ -748,7 +748,7 @@ export function Root(props: IProps): React.ReactElement {
"Do you want to overwrite the current editor content with the contents of " + "Do you want to overwrite the current editor content with the contents of " +
openScript.fileName + openScript.fileName +
" on the server? This cannot be undone.", " on the server? This cannot be undone.",
resolve: (result: boolean) => { resolve: (result: boolean | string) => {
if (result) { if (result) {
// Save changes // Save changes
openScript.code = serverScriptCode; openScript.code = serverScriptCode;

@ -1,14 +1,19 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, Dispatch, SetStateAction } from "react";
import { EventEmitter } from "../../utils/EventEmitter"; import { EventEmitter } from "../../utils/EventEmitter";
import { Modal } from "./Modal"; import { Modal } from "./Modal";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import Select, { SelectChangeEvent } from "@mui/material/Select";
import TextField from '@mui/material/TextField';
import { KEY } from '../../utils/helpers/keyCodes';
import MenuItem from '@mui/material/MenuItem';
export const PromptEvent = new EventEmitter<[Prompt]>(); export const PromptEvent = new EventEmitter<[Prompt]>();
interface Prompt { interface Prompt {
txt: string; txt: string;
resolve: (result: boolean) => void; options?: { type?: string; choices?: string[] | { [key: string]: string | number } };
resolve: (result: boolean | string) => void;
} }
export function PromptManager(): React.ReactElement { export function PromptManager(): React.ReactElement {
@ -21,36 +26,150 @@ export function PromptManager(): React.ReactElement {
[], [],
); );
const valueState = useState('')
function close(): void { function close(): void {
if (prompt === null) return; if (prompt === null) return;
prompt.resolve(false); prompt.resolve(false);
valueState[1]('')
setPrompt(null); setPrompt(null);
} }
function yes(): void { let promptRenderer;
if (prompt === null) return; switch (prompt?.options?.type) {
prompt.resolve(true); case 'text': {
setPrompt(null); promptRenderer = promptMenuText;
break;
}
case 'select': {
promptRenderer = promptMenuSelect;
break;
}
default: {
promptRenderer = promptMenuBoolean;
} }
function no(): void {
if (prompt === null) return;
prompt.resolve(false);
setPrompt(null);
} }
return ( return (
<> <>
{prompt != null && ( {prompt != null && (
<Modal open={true} onClose={close}> <Modal open={true} onClose={close}>
<pre>
<Typography>{prompt.txt}</Typography> <Typography>{prompt.txt}</Typography>
</pre> {promptRenderer(prompt, setPrompt, valueState)}
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', paddingTop: '10px' }}>
<Button style={{ marginRight: 'auto' }} onClick={yes}>Yes</Button>
<Button onClick={no}>No</Button>
</div>
</Modal> </Modal>
)} )}
</> </>
); );
} }
function promptMenuBoolean(prompt: Prompt | null, setPrompt: Dispatch<SetStateAction<Prompt | null>>): React.ReactElement {
const yes = (): void => {
if (prompt !== null) {
prompt.resolve(true);
setPrompt(null);
}
}
const no = (): void => {
if (prompt !== null) {
prompt.resolve(false);
setPrompt(null);
}
}
return (
<>
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', paddingTop: '10px' }}>
<Button style={{ marginRight: 'auto' }} onClick={yes}>Yes</Button>
<Button onClick={no}>No</Button>
</div>
</>
);
}
function promptMenuText(prompt: Prompt | null, setPrompt: Dispatch<SetStateAction<Prompt | null>>, valueState: [string, Dispatch<SetStateAction<string>>]): React.ReactElement {
const [value, setValue] = valueState
const submit = (): void => {
if (prompt !== null) {
prompt.resolve(value);
setValue('')
setPrompt(null);
}
}
const onInput = (event: React.ChangeEvent<HTMLInputElement>): void => {
setValue(event.target.value);
}
const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {
event.stopPropagation();
if (prompt !== null && event.keyCode === KEY.ENTER) {
event.preventDefault();
submit();
}
}
return (
<>
<div style={{ display: 'flex', alignItems: 'center', paddingTop: '10px' }}>
<TextField
autoFocus
value={value}
onInput={onInput}
onKeyDown={onKeyDown}
style={{ flex: '1 0 auto' }}
InputProps={{
endAdornment: (
<Button
onClick={() => {
submit();
}}
>
Confirm
</Button>
),
}}
/>
</div>
</>
);
}
function promptMenuSelect(prompt: Prompt | null, setPrompt: Dispatch<SetStateAction<Prompt | null>>, valueState: [string, Dispatch<SetStateAction<string>>]): React.ReactElement {
const [value, setValue] = valueState
const submit = (): void => {
if (prompt !== null) {
prompt.resolve(value);
setValue('');
setPrompt(null);
}
}
const onChange = (event: SelectChangeEvent<string>): void => {
setValue(event.target.value);
}
const getItems = (choices: string[] | { [key: string]: string | number }) : React.ReactElement[] => {
const content = [];
for (const i in choices) {
// @ts-ignore
content.push(<MenuItem value={i}>{choices[i]}</MenuItem>);
}
return content;
}
return (
<>
<div style={{ display: 'flex', alignItems: 'center', paddingTop: '10px' }}>
<Select onChange={onChange} value={value} style={{ flex: '1 0 auto' }}>
{getItems(prompt?.options?.choices || [])}
</Select>
<Button onClick={submit}>Confirm</Button>
</div>
</>
);
}