From 08d0b4cbae6bd876eed179ffff001aff7e0a02b8 Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 30 Jan 2022 14:06:18 +0000 Subject: [PATCH 1/3] Adding support for text/select options in Prompt command --- src/NetscriptFunctions.ts | 5 +- src/ScriptEditor/ui/ScriptEditorRoot.tsx | 4 +- src/ui/React/PromptManager.tsx | 150 ++++++++++++++++++++--- 3 files changed, 140 insertions(+), 19 deletions(-) diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index ae6cd479e..4a95ad2aa 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -1654,7 +1654,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS { }else{ workerScript.log("purchaseServer", () => `Invalid argument: ram='${ram}' must be a positive power of 2`); } - + return ""; } @@ -2116,7 +2116,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 { if (!isString(txt)) { txt = JSON.stringify(txt); } @@ -2124,6 +2124,7 @@ export function NetscriptFunctions(workerScript: WorkerScript): NS { return new Promise(function (resolve) { PromptEvent.emit({ txt: txt, + options, resolve: resolve, }); }); diff --git a/src/ScriptEditor/ui/ScriptEditorRoot.tsx b/src/ScriptEditor/ui/ScriptEditorRoot.tsx index a53823671..098bba4a4 100644 --- a/src/ScriptEditor/ui/ScriptEditorRoot.tsx +++ b/src/ScriptEditor/ui/ScriptEditorRoot.tsx @@ -649,7 +649,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; @@ -704,7 +704,7 @@ export function Root(props: IProps): React.ReactElement { PromptEvent.emit({ txt: "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 113131812..887700db8 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 "../../ui/React/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; options?: string[] }; + resolve: (result: boolean | string) => void; } export function PromptManager(): React.ReactElement { @@ -27,28 +32,143 @@ export function PromptManager(): React.ReactElement { 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; + } } + const valueState = useState('') + return ( <> {prompt != null && ( {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 = (options: string[] | { [key: string]: string }) : React.ReactElement[] => { + const content = []; + for (const i in options) { + // @ts-ignore + content.push({options[i]}); + } + return content; + } + + return ( + <> +
+ + +
+ + ); +} From af43e634154a2580cc29507605e4366b75763f39 Mon Sep 17 00:00:00 2001 From: Phil Date: Sun, 30 Jan 2022 14:21:46 +0000 Subject: [PATCH 2/3] Blanking Prompt value on close --- src/ui/React/PromptManager.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ui/React/PromptManager.tsx b/src/ui/React/PromptManager.tsx index 887700db8..2df89406e 100644 --- a/src/ui/React/PromptManager.tsx +++ b/src/ui/React/PromptManager.tsx @@ -26,9 +26,12 @@ export function PromptManager(): React.ReactElement { [], ); + const valueState = useState('') + function close(): void { if (prompt === null) return; prompt.resolve(false); + valueState[1]('') setPrompt(null); } @@ -49,8 +52,6 @@ export function PromptManager(): React.ReactElement { } } - const valueState = useState('') - return ( <> {prompt != null && ( From 378f67097c69a720f619f31a32e08e3091e0bff6 Mon Sep 17 00:00:00 2001 From: Phil Date: Mon, 31 Jan 2022 18:15:13 +0000 Subject: [PATCH 3/3] Amending NetscriptDefinitions --- src/ScriptEditor/NetscriptDefinitions.d.ts | 17 ++++++++++------- src/ui/React/PromptManager.tsx | 10 +++++----- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/ScriptEditor/NetscriptDefinitions.d.ts b/src/ScriptEditor/NetscriptDefinitions.d.ts index 6e613ba80..a0e9bc184 100644 --- a/src/ScriptEditor/NetscriptDefinitions.d.ts +++ b/src/ScriptEditor/NetscriptDefinitions.d.ts @@ -5878,19 +5878,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/ui/React/PromptManager.tsx b/src/ui/React/PromptManager.tsx index 2df89406e..384616396 100644 --- a/src/ui/React/PromptManager.tsx +++ b/src/ui/React/PromptManager.tsx @@ -12,7 +12,7 @@ export const PromptEvent = new EventEmitter<[Prompt]>(); interface Prompt { txt: string; - options?: { type?: string; options?: string[] }; + options?: { type?: string; choices?: string[] | { [key: string]: string | number } }; resolve: (result: boolean | string) => void; } @@ -153,11 +153,11 @@ function promptMenuSelect(prompt: Prompt | null, setPrompt: Dispatch { + const getItems = (choices: string[] | { [key: string]: string | number }) : React.ReactElement[] => { const content = []; - for (const i in options) { + for (const i in choices) { // @ts-ignore - content.push({options[i]}); + content.push({choices[i]}); } return content; } @@ -166,7 +166,7 @@ function promptMenuSelect(prompt: Prompt | null, setPrompt: Dispatch