diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index 7b111452f..ca35bf3fd 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -2152,7 +2152,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS { message = argsToString([message]); 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")); if (!isString(txt)) { txt = JSON.stringify(txt); @@ -2161,6 +2161,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS { return new Promise(function (resolve) { PromptEvent.emit({ txt: txt, + options, resolve: resolve, }); }); diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 3f9fef549..108900077 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -5984,19 +5984,22 @@ export interface NS extends Singularity { tFormat(milliseconds: number, milliPrecision?: boolean): string; /** - * Prompt the player with a Yes/No modal. + * Prompt the player with an input modal. * @remarks * RAM cost: 0 GB * - * Prompts the player with a dialog box with two options: “Yes” and “No”. - * This function will return true if the player click “Yes” and false if - * the player clicks “No”. The script’s execution is halted until the player - * selects one of the options. + * Prompts the player with a dialog box. If `options.type` is undefined or "boolean", + * the player is shown "Yes" and "No" prompts, which return true and false respectively. + * Passing a type of "text" will give the player a text field and a value of "select" + * will show a drop-down field. Choosing type "select" will require an array or object + * to be passed via the `options.choices` property. + * The script’s execution is halted until the player selects one of the options. * * @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; + prompt(txt: string, options?: { type?: "boolean"|"text"|"select"|undefined; choices?: string[] | { [key: string]: string | number } }): Promise; /** * Open up a message box. diff --git a/src/ScriptEditor/ui/ScriptEditorRoot.tsx b/src/ScriptEditor/ui/ScriptEditorRoot.tsx index 7e7292c85..8470d6b12 100644 --- a/src/ScriptEditor/ui/ScriptEditorRoot.tsx +++ b/src/ScriptEditor/ui/ScriptEditorRoot.tsx @@ -691,7 +691,7 @@ export function Root(props: IProps): React.ReactElement { if (serverScriptIndex === -1 || savedScriptCode !== server.scripts[serverScriptIndex as number].code) { PromptEvent.emit({ txt: "Do you want to save changes to " + closingScript.fileName + "?", - resolve: (result: boolean) => { + resolve: (result: boolean | string) => { if (result) { // Save changes 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 " + openScript.fileName + " on the server? This cannot be undone.", - resolve: (result: boolean) => { + resolve: (result: boolean | string) => { if (result) { // Save changes openScript.code = serverScriptCode; diff --git a/src/ui/React/PromptManager.tsx b/src/ui/React/PromptManager.tsx index 6acc669c0..a4fc5a7b4 100644 --- a/src/ui/React/PromptManager.tsx +++ b/src/ui/React/PromptManager.tsx @@ -1,14 +1,19 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, Dispatch, SetStateAction } from "react"; import { EventEmitter } from "../../utils/EventEmitter"; import { Modal } from "./Modal"; import Typography from "@mui/material/Typography"; 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]>(); interface Prompt { 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 { @@ -21,36 +26,150 @@ export function PromptManager(): React.ReactElement { [], ); + const valueState = useState('') + function close(): void { if (prompt === null) return; prompt.resolve(false); + valueState[1]('') setPrompt(null); } - function yes(): void { - if (prompt === null) return; - prompt.resolve(true); - setPrompt(null); - } - function no(): void { - if (prompt === null) return; - prompt.resolve(false); - setPrompt(null); + let promptRenderer; + switch (prompt?.options?.type) { + case 'text': { + promptRenderer = promptMenuText; + break; + } + + case 'select': { + promptRenderer = promptMenuSelect; + break; + } + + default: { + promptRenderer = promptMenuBoolean; + } } return ( <> {prompt != null && ( -
-            {prompt.txt}
-          
-
- - -
+ {prompt.txt} + {promptRenderer(prompt, setPrompt, valueState)}
)} ); } + +function promptMenuBoolean(prompt: Prompt | null, setPrompt: Dispatch>): 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 ( + <> +
+ + +
+ + ); +} + +function promptMenuText(prompt: Prompt | null, setPrompt: Dispatch>, valueState: [string, Dispatch>]): React.ReactElement { + const [value, setValue] = valueState + + const submit = (): void => { + if (prompt !== null) { + prompt.resolve(value); + setValue('') + setPrompt(null); + } + } + + const onInput = (event: React.ChangeEvent): void => { + setValue(event.target.value); + } + + const onKeyDown = (event: React.KeyboardEvent): void => { + event.stopPropagation(); + + if (prompt !== null && event.keyCode === KEY.ENTER) { + event.preventDefault(); + submit(); + } + } + + return ( + <> +
+ { + submit(); + }} + > + Confirm + + ), + }} + /> +
+ + ); +} + +function promptMenuSelect(prompt: Prompt | null, setPrompt: Dispatch>, valueState: [string, Dispatch>]): React.ReactElement { + const [value, setValue] = valueState + + const submit = (): void => { + if (prompt !== null) { + prompt.resolve(value); + setValue(''); + setPrompt(null); + } + } + + const onChange = (event: SelectChangeEvent): 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({choices[i]}); + } + return content; + } + + return ( + <> +
+ + +
+ + ); +}