Make prompt() to return null on cancel

This commit is contained in:
Gregory Schier
2024-10-02 05:54:44 -07:00
parent 89ff25cd54
commit 4160e5b1c4
26 changed files with 436 additions and 108 deletions

View File

@@ -19,7 +19,7 @@
"start": "npm run app-dev", "start": "npm run app-dev",
"app-build": "tauri build", "app-build": "tauri build",
"app-dev": "tauri dev --no-watch --config ./src-tauri/tauri-dev.conf.json", "app-dev": "tauri dev --no-watch --config ./src-tauri/tauri-dev.conf.json",
"bootstrap": "run-p bootstrap:* && npm run --workspace plugin-runtime build", "bootstrap": "run-p bootstrap:* && npm run --workspaces --if-present bootstrap",
"bootstrap:vendor-node": "node scripts/vendor-node.cjs", "bootstrap:vendor-node": "node scripts/vendor-node.cjs",
"bootstrap:vendor-plugins": "node scripts/vendor-plugins.cjs", "bootstrap:vendor-plugins": "node scripts/vendor-plugins.cjs",
"bootstrap:vendor-protoc": "node scripts/vendor-protoc.cjs", "bootstrap:vendor-protoc": "node scripts/vendor-protoc.cjs",

View File

@@ -1,12 +1,13 @@
{ {
"name": "@yaakapp/api", "name": "@yaakapp/api",
"version": "0.2.13", "version": "0.2.15",
"main": "lib/index.js", "main": "lib/index.js",
"typings": "./lib/index.d.ts", "typings": "./lib/index.d.ts",
"files": [ "files": [
"lib/**/*" "lib/**/*"
], ],
"scripts": { "scripts": {
"bootstrap": "npm run build",
"build": "run-s build:copy-types build:tsc", "build": "run-s build:copy-types build:tsc",
"build:tsc": "tsc", "build:tsc": "tsc",
"build:copy-types": "run-p build:copy-types:*", "build:copy-types": "run-p build:copy-types:*",

View File

@@ -59,21 +59,15 @@ export type ImportResponse = { resources: ImportResources, };
export type InternalEvent = { id: string, pluginRefId: string, replyId: string | null, payload: InternalEventPayload, windowContext: WindowContext, }; export type InternalEvent = { id: string, pluginRefId: string, replyId: string | null, payload: InternalEventPayload, windowContext: WindowContext, };
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } | { "type": "reload_response" } | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & GetHttpRequestActionsRequest | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_prompt_request" } & ShowPromptRequest | { "type": "show_prompt_response" } & ShowPromptResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" }; export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } | { "type": "reload_response" } | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & GetHttpRequestActionsRequest | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" };
export type OpenFileFilter = { name: string, extensions: Array<string>, }; export type OpenFileFilter = { name: string,
/**
* File extensions to require
*/
extensions: Array<string>, };
export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, }; export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
export type RenderHttpRequestResponse = { httpRequest: HttpRequest, };
export type RenderPurpose = "send" | "preview";
export type SendHttpRequestRequest = { httpRequest: HttpRequest, };
export type SendHttpRequestResponse = { httpResponse: HttpResponse, };
export type ShowPromptRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
/** /**
* Text to add to the confirmation button * Text to add to the confirmation button
*/ */
@@ -87,7 +81,17 @@ cancelText?: string,
*/ */
require?: boolean, }; require?: boolean, };
export type ShowPromptResponse = { value: string, }; export type PromptTextResponse = { value: string | null, };
export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, };
export type RenderHttpRequestResponse = { httpRequest: HttpRequest, };
export type RenderPurpose = "send" | "preview";
export type SendHttpRequestRequest = { httpRequest: HttpRequest, };
export type SendHttpRequestResponse = { httpResponse: HttpResponse, };
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, }; export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, };
@@ -95,19 +99,131 @@ export type TemplateFunction = { name: string, aliases?: Array<string>, args: Ar
export type TemplateFunctionArg = { "type": "text" } & TemplateFunctionTextArg | { "type": "select" } & TemplateFunctionSelectArg | { "type": "checkbox" } & TemplateFunctionCheckboxArg | { "type": "http_request" } & TemplateFunctionHttpRequestArg | { "type": "file" } & TemplateFunctionFileArg; export type TemplateFunctionArg = { "type": "text" } & TemplateFunctionTextArg | { "type": "select" } & TemplateFunctionSelectArg | { "type": "checkbox" } & TemplateFunctionCheckboxArg | { "type": "http_request" } & TemplateFunctionHttpRequestArg | { "type": "file" } & TemplateFunctionFileArg;
export type TemplateFunctionBaseArg = { name: string, optional?: boolean, label?: string, defaultValue?: string, }; export type TemplateFunctionBaseArg = {
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionCheckboxArg = { name: string, optional?: boolean, label?: string, defaultValue?: string, }; export type TemplateFunctionCheckboxArg = {
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionFileArg = { title: string, multiple?: boolean, directory?: boolean, defaultPath?: string, filters?: Array<OpenFileFilter>, name: string, optional?: boolean, label?: string, defaultValue?: string, }; export type TemplateFunctionFileArg = {
/**
* The title of the file selection window
*/
title: string,
/**
* Allow selecting multiple files
*/
multiple?: boolean, directory?: boolean, defaultPath?: string, filters?: Array<OpenFileFilter>,
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionHttpRequestArg = { name: string, optional?: boolean, label?: string, defaultValue?: string, }; export type TemplateFunctionHttpRequestArg = {
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionSelectArg = { options: Array<TemplateFunctionSelectOption>, name: string, optional?: boolean, label?: string, defaultValue?: string, }; export type TemplateFunctionSelectArg = {
/**
* The options that will be available in the select input
*/
options: Array<TemplateFunctionSelectOption>,
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionSelectOption = { name: string, value: string, }; export type TemplateFunctionSelectOption = { label: string, value: string, };
export type TemplateFunctionTextArg = { placeholder?: string, name: string, optional?: boolean, label?: string, defaultValue?: string, }; export type TemplateFunctionTextArg = {
/**
* Placeholder for the text input
*/
placeholder?: string,
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, }; export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, };

View File

@@ -1,17 +1,17 @@
import { import {
ShowPromptRequest,
ShowPromptResponse,
TemplateRenderRequest,
TemplateRenderResponse,
FindHttpResponsesRequest, FindHttpResponsesRequest,
FindHttpResponsesResponse, FindHttpResponsesResponse,
GetHttpRequestByIdRequest, GetHttpRequestByIdRequest,
GetHttpRequestByIdResponse, GetHttpRequestByIdResponse,
PromptTextRequest,
PromptTextResponse,
RenderHttpRequestRequest, RenderHttpRequestRequest,
RenderHttpRequestResponse, RenderHttpRequestResponse,
SendHttpRequestRequest, SendHttpRequestRequest,
SendHttpRequestResponse, SendHttpRequestResponse,
ShowToastRequest, ShowToastRequest,
TemplateRenderRequest,
TemplateRenderResponse,
} from '..'; } from '..';
export type Context = { export type Context = {
@@ -22,7 +22,7 @@ export type Context = {
show(args: ShowToastRequest): void; show(args: ShowToastRequest): void;
}; };
prompt: { prompt: {
show(args: ShowPromptRequest): Promise<ShowPromptResponse['value']>; text(args: PromptTextRequest): Promise<PromptTextResponse['value']>;
}; };
httpRequest: { httpRequest: {
send(args: SendHttpRequestRequest): Promise<SendHttpRequestResponse['httpResponse']>; send(args: SendHttpRequestRequest): Promise<SendHttpRequestResponse['httpResponse']>;

View File

@@ -1,6 +1,7 @@
{ {
"name": "@yaakapp-internal/plugin-runtime", "name": "@yaakapp-internal/plugin-runtime",
"scripts": { "scripts": {
"bootstrap": "npm run build",
"build": "run-p build:*", "build": "run-p build:*",
"build:main": "esbuild src/index.ts --bundle --platform=node --outfile=../src-tauri/vendored/plugin-runtime/index.cjs", "build:main": "esbuild src/index.ts --bundle --platform=node --outfile=../src-tauri/vendored/plugin-runtime/index.cjs",
"build:worker": "esbuild src/index.worker.ts --bundle --platform=node --outfile=../src-tauri/vendored/plugin-runtime/index.worker.cjs", "build:worker": "esbuild src/index.worker.ts --bundle --platform=node --outfile=../src-tauri/vendored/plugin-runtime/index.worker.cjs",

View File

@@ -1,21 +1,19 @@
import {
RenderHttpRequestResponse,
TemplateRenderResponse,
WindowContext,
} from '@yaakapp-internal/plugin';
import { import {
BootRequest, BootRequest,
Context,
FindHttpResponsesResponse, FindHttpResponsesResponse,
GetHttpRequestByIdResponse, GetHttpRequestByIdResponse,
HttpRequestAction, HttpRequestAction,
ImportResponse, ImportResponse,
InternalEvent, InternalEvent,
InternalEventPayload, InternalEventPayload,
PromptTextResponse,
RenderHttpRequestResponse,
SendHttpRequestResponse, SendHttpRequestResponse,
ShowPromptResponse,
TemplateFunction, TemplateFunction,
} from '@yaakapp/api'; TemplateRenderResponse,
WindowContext,
} from '@yaakapp-internal/plugin';
import { Context } from '@yaakapp/api';
import { HttpRequestActionPlugin } from '@yaakapp/api/lib/plugins/HttpRequestActionPlugin'; import { HttpRequestActionPlugin } from '@yaakapp/api/lib/plugins/HttpRequestActionPlugin';
import { TemplateFunctionPlugin } from '@yaakapp/api/lib/plugins/TemplateFunctionPlugin'; import { TemplateFunctionPlugin } from '@yaakapp/api/lib/plugins/TemplateFunctionPlugin';
import interceptStdout from 'intercept-stdout'; import interceptStdout from 'intercept-stdout';
@@ -140,9 +138,9 @@ async function initialize() {
}, },
}, },
prompt: { prompt: {
async show(args) { async text(args) {
const reply: ShowPromptResponse = await sendAndWaitForReply(event.windowContext, { const reply: PromptTextResponse = await sendAndWaitForReply(event.windowContext, {
type: 'show_prompt_request', type: 'prompt_text_request',
...args, ...args,
}); });
return reply.value; return reply.value;

View File

@@ -45,6 +45,7 @@ pub async fn send_http_request<R: Runtime>(
&WindowContext::from_window(window), &WindowContext::from_window(window),
RenderPurpose::Send, RenderPurpose::Send,
); );
let rendered_request = let rendered_request =
render_http_request(&request, &workspace, environment.as_ref(), &cb).await; render_http_request(&request, &workspace, environment.as_ref(), &cb).await;

View File

@@ -63,7 +63,7 @@ use yaak_plugin_runtime::events::{
BootResponse, CallHttpRequestActionRequest, FilterResponse, FindHttpResponsesResponse, BootResponse, CallHttpRequestActionRequest, FilterResponse, FindHttpResponsesResponse,
GetHttpRequestActionsResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, Icon, GetHttpRequestActionsResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, Icon,
InternalEvent, InternalEventPayload, RenderHttpRequestResponse, RenderPurpose, InternalEvent, InternalEventPayload, RenderHttpRequestResponse, RenderPurpose,
SendHttpRequestResponse, ShowPromptResponse, ShowToastRequest, TemplateRenderResponse, SendHttpRequestResponse, PromptTextResponse, ShowToastRequest, TemplateRenderResponse,
WindowContext, WindowContext,
}; };
use yaak_plugin_runtime::plugin_handle::PluginHandle; use yaak_plugin_runtime::plugin_handle::PluginHandle;
@@ -1081,6 +1081,18 @@ async fn cmd_send_http_request(
// that has not yet been saved in the DB. // that has not yet been saved in the DB.
request: HttpRequest, request: HttpRequest,
) -> Result<HttpResponse, String> { ) -> Result<HttpResponse, String> {
let response = create_default_http_response(&window, &request.id)
.await
.map_err(|e| e.to_string())?;
let (cancel_tx, mut cancel_rx) = tokio::sync::watch::channel(false);
window.listen_any(
format!("cancel_http_response_{}", response.id),
move |_event| {
let _ = cancel_tx.send(true);
},
);
let environment = match environment_id { let environment = match environment_id {
Some(id) => match get_environment(&window, id).await { Some(id) => match get_environment(&window, id).await {
Ok(env) => Some(env), Ok(env) => Some(env),
@@ -1101,18 +1113,6 @@ async fn cmd_send_http_request(
None => None, None => None,
}; };
let response = create_default_http_response(&window, &request.id)
.await
.map_err(|e| e.to_string())?;
let (cancel_tx, mut cancel_rx) = tokio::sync::watch::channel(false);
window.listen_any(
format!("cancel_http_response_{}", response.id),
move |_event| {
let _ = cancel_tx.send(true);
},
);
send_http_request( send_http_request(
&window, &window,
&request, &request,
@@ -2171,18 +2171,18 @@ async fn call_frontend<T: Serialize + Clone, R: Runtime>(
window: WebviewWindow<R>, window: WebviewWindow<R>,
event_name: &str, event_name: &str,
args: T, args: T,
) -> ShowPromptResponse { ) -> PromptTextResponse {
let reply_id = format!("{event_name}_reply"); let reply_id = format!("{event_name}_reply");
let payload = FrontendCall { let payload = FrontendCall {
args, args,
reply_id: reply_id.clone(), reply_id: reply_id.clone(),
}; };
window.emit_to(window.label(), event_name, payload).unwrap(); window.emit_to(window.label(), event_name, payload).unwrap();
let (tx, mut rx) = tokio::sync::watch::channel(ShowPromptResponse::default()); let (tx, mut rx) = tokio::sync::watch::channel(PromptTextResponse::default());
let event_id = window.clone().listen(reply_id, move |ev| { let event_id = window.clone().listen(reply_id, move |ev| {
println!("GOT REPLY {ev:?}"); println!("GOT REPLY {ev:?}");
let resp: ShowPromptResponse = serde_json::from_str(ev.payload()).unwrap(); let resp: PromptTextResponse = serde_json::from_str(ev.payload()).unwrap();
_ = tx.send(resp); _ = tx.send(resp);
}); });
@@ -2220,11 +2220,11 @@ async fn handle_plugin_event<R: Runtime>(
}; };
None None
} }
InternalEventPayload::ShowPromptRequest(req) => { InternalEventPayload::PromptTextRequest(req) => {
let window = get_window_from_window_context(app_handle, &window_context) let window = get_window_from_window_context(app_handle, &window_context)
.expect("Failed to find window for render"); .expect("Failed to find window for render");
let resp = call_frontend(window, "show_prompt", req).await; let resp = call_frontend(window, "show_prompt", req).await;
Some(InternalEventPayload::ShowPromptResponse(resp)) Some(InternalEventPayload::PromptTextResponse(resp))
} }
InternalEventPayload::FindHttpResponsesRequest(req) => { InternalEventPayload::FindHttpResponsesRequest(req) => {
let http_responses = list_http_responses( let http_responses = list_http_responses(

View File

@@ -59,21 +59,15 @@ export type ImportResponse = { resources: ImportResources, };
export type InternalEvent = { id: string, pluginRefId: string, replyId: string | null, payload: InternalEventPayload, windowContext: WindowContext, }; export type InternalEvent = { id: string, pluginRefId: string, replyId: string | null, payload: InternalEventPayload, windowContext: WindowContext, };
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } | { "type": "reload_response" } | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & GetHttpRequestActionsRequest | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_prompt_request" } & ShowPromptRequest | { "type": "show_prompt_response" } & ShowPromptResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" }; export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } | { "type": "reload_response" } | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & GetHttpRequestActionsRequest | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" };
export type OpenFileFilter = { name: string, extensions: Array<string>, }; export type OpenFileFilter = { name: string,
/**
* File extensions to require
*/
extensions: Array<string>, };
export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, }; export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
export type RenderHttpRequestResponse = { httpRequest: HttpRequest, };
export type RenderPurpose = "send" | "preview";
export type SendHttpRequestRequest = { httpRequest: HttpRequest, };
export type SendHttpRequestResponse = { httpResponse: HttpResponse, };
export type ShowPromptRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
/** /**
* Text to add to the confirmation button * Text to add to the confirmation button
*/ */
@@ -87,7 +81,17 @@ cancelText?: string,
*/ */
require?: boolean, }; require?: boolean, };
export type ShowPromptResponse = { value: string, }; export type PromptTextResponse = { value: string | null, };
export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, };
export type RenderHttpRequestResponse = { httpRequest: HttpRequest, };
export type RenderPurpose = "send" | "preview";
export type SendHttpRequestRequest = { httpRequest: HttpRequest, };
export type SendHttpRequestResponse = { httpResponse: HttpResponse, };
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, }; export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, };
@@ -95,19 +99,131 @@ export type TemplateFunction = { name: string, aliases?: Array<string>, args: Ar
export type TemplateFunctionArg = { "type": "text" } & TemplateFunctionTextArg | { "type": "select" } & TemplateFunctionSelectArg | { "type": "checkbox" } & TemplateFunctionCheckboxArg | { "type": "http_request" } & TemplateFunctionHttpRequestArg | { "type": "file" } & TemplateFunctionFileArg; export type TemplateFunctionArg = { "type": "text" } & TemplateFunctionTextArg | { "type": "select" } & TemplateFunctionSelectArg | { "type": "checkbox" } & TemplateFunctionCheckboxArg | { "type": "http_request" } & TemplateFunctionHttpRequestArg | { "type": "file" } & TemplateFunctionFileArg;
export type TemplateFunctionBaseArg = { name: string, optional?: boolean, label?: string, defaultValue?: string, }; export type TemplateFunctionBaseArg = {
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionCheckboxArg = { name: string, optional?: boolean, label?: string, defaultValue?: string, }; export type TemplateFunctionCheckboxArg = {
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionFileArg = { title: string, multiple?: boolean, directory?: boolean, defaultPath?: string, filters?: Array<OpenFileFilter>, name: string, optional?: boolean, label?: string, defaultValue?: string, }; export type TemplateFunctionFileArg = {
/**
* The title of the file selection window
*/
title: string,
/**
* Allow selecting multiple files
*/
multiple?: boolean, directory?: boolean, defaultPath?: string, filters?: Array<OpenFileFilter>,
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionHttpRequestArg = { name: string, optional?: boolean, label?: string, defaultValue?: string, }; export type TemplateFunctionHttpRequestArg = {
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionSelectArg = { options: Array<TemplateFunctionSelectOption>, name: string, optional?: boolean, label?: string, defaultValue?: string, }; export type TemplateFunctionSelectArg = {
/**
* The options that will be available in the select input
*/
options: Array<TemplateFunctionSelectOption>,
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionSelectOption = { name: string, value: string, }; export type TemplateFunctionSelectOption = { label: string, value: string, };
export type TemplateFunctionTextArg = { placeholder?: string, name: string, optional?: boolean, label?: string, defaultValue?: string, }; export type TemplateFunctionTextArg = {
/**
* Placeholder for the text input
*/
placeholder?: string,
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, }; export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, };

View File

@@ -76,8 +76,8 @@ pub enum InternalEventPayload {
ShowToastRequest(ShowToastRequest), ShowToastRequest(ShowToastRequest),
ShowPromptRequest(ShowPromptRequest), PromptTextRequest(PromptTextRequest),
ShowPromptResponse(ShowPromptResponse), PromptTextResponse(PromptTextResponse),
GetHttpRequestByIdRequest(GetHttpRequestByIdRequest), GetHttpRequestByIdRequest(GetHttpRequestByIdRequest),
GetHttpRequestByIdResponse(GetHttpRequestByIdResponse), GetHttpRequestByIdResponse(GetHttpRequestByIdResponse),
@@ -215,7 +215,7 @@ pub struct ShowToastRequest {
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub struct ShowPromptRequest { pub struct PromptTextRequest {
// A unique ID to identify the prompt (eg. "enter-password") // A unique ID to identify the prompt (eg. "enter-password")
pub id: String, pub id: String,
// Title to show on the prompt dialog // Title to show on the prompt dialog
@@ -242,8 +242,8 @@ pub struct ShowPromptRequest {
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub struct ShowPromptResponse { pub struct PromptTextResponse {
pub value: String, pub value: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Serialize, Deserialize, TS)]
@@ -294,6 +294,9 @@ pub struct GetTemplateFunctionsResponse {
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub struct TemplateFunction { pub struct TemplateFunction {
pub name: String, pub name: String,
/// Also support alternative names. This is useful for not breaking existing
/// tags when changing the `name` property
#[ts(optional)] #[ts(optional)]
pub aliases: Option<Vec<String>>, pub aliases: Option<Vec<String>>,
pub args: Vec<TemplateFunctionArg>, pub args: Vec<TemplateFunctionArg>,
@@ -314,11 +317,18 @@ pub enum TemplateFunctionArg {
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub struct TemplateFunctionBaseArg { pub struct TemplateFunctionBaseArg {
/// The name of the argument. Should be `camelCase` format
pub name: String, pub name: String,
/// Whether the user must fill in the argument
#[ts(optional)] #[ts(optional)]
pub optional: Option<bool>, pub optional: Option<bool>,
/// The label of the input
#[ts(optional)] #[ts(optional)]
pub label: Option<String>, pub label: Option<String>,
/// The default value
#[ts(optional)] #[ts(optional)]
pub default_value: Option<String>, pub default_value: Option<String>,
} }
@@ -329,6 +339,8 @@ pub struct TemplateFunctionBaseArg {
pub struct TemplateFunctionTextArg { pub struct TemplateFunctionTextArg {
#[serde(flatten)] #[serde(flatten)]
pub base: TemplateFunctionBaseArg, pub base: TemplateFunctionBaseArg,
/// Placeholder for the text input
#[ts(optional)] #[ts(optional)]
pub placeholder: Option<String>, pub placeholder: Option<String>,
} }
@@ -347,13 +359,23 @@ pub struct TemplateFunctionHttpRequestArg {
pub struct TemplateFunctionFileArg { pub struct TemplateFunctionFileArg {
#[serde(flatten)] #[serde(flatten)]
pub base: TemplateFunctionBaseArg, pub base: TemplateFunctionBaseArg,
/// The title of the file selection window
pub title: String, pub title: String,
/// Allow selecting multiple files
#[ts(optional)] #[ts(optional)]
pub multiple: Option<bool>, pub multiple: Option<bool>,
// Select a directory, not a file
#[ts(optional)] #[ts(optional)]
pub directory: Option<bool>, pub directory: Option<bool>,
// Default file path for selection dialog
#[ts(optional)] #[ts(optional)]
pub default_path: Option<String>, pub default_path: Option<String>,
// Specify to only allow selection of certain file extensions
#[ts(optional)] #[ts(optional)]
pub filters: Option<Vec<OpenFileFilter>>, pub filters: Option<Vec<OpenFileFilter>>,
} }
@@ -363,6 +385,7 @@ pub struct TemplateFunctionFileArg {
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub struct OpenFileFilter { pub struct OpenFileFilter {
pub name: String, pub name: String,
/// File extensions to require
pub extensions: Vec<String>, pub extensions: Vec<String>,
} }
@@ -372,6 +395,8 @@ pub struct OpenFileFilter {
pub struct TemplateFunctionSelectArg { pub struct TemplateFunctionSelectArg {
#[serde(flatten)] #[serde(flatten)]
pub base: TemplateFunctionBaseArg, pub base: TemplateFunctionBaseArg,
/// The options that will be available in the select input
pub options: Vec<TemplateFunctionSelectOption>, pub options: Vec<TemplateFunctionSelectOption>,
} }
@@ -387,7 +412,7 @@ pub struct TemplateFunctionCheckboxArg {
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub struct TemplateFunctionSelectOption { pub struct TemplateFunctionSelectOption {
pub name: String, pub label: String,
pub value: String, pub value: String,
} }

View File

@@ -64,6 +64,7 @@ export function CookieDropdown() {
placeholder: 'New name', placeholder: 'New name',
defaultValue: activeCookieJar?.name, defaultValue: activeCookieJar?.name,
}); });
if (name == null) return;
updateCookieJar.mutate({ name }); updateCookieJar.mutate({ name });
}, },
}, },

View File

@@ -6,7 +6,7 @@ import { Dialog } from './core/Dialog';
type DialogEntry = { type DialogEntry = {
id: string; id: string;
render: ({ hide }: { hide: () => void }) => React.ReactNode; render: ({ hide }: { hide: () => void }) => React.ReactNode;
} & Omit<DialogProps, 'onClose' | 'open' | 'children'>; } & Omit<DialogProps, 'open' | 'children'>;
interface State { interface State {
dialogs: DialogEntry[]; dialogs: DialogEntry[];

View File

@@ -279,6 +279,7 @@ function SidebarButton({
placeholder: 'New Name', placeholder: 'New Name',
defaultValue: environment.name, defaultValue: environment.name,
}); });
if (name == null) return;
updateEnvironment.mutate({ name }); updateEnvironment.mutate({ name });
}, },
}, },

View File

@@ -2,7 +2,7 @@ import { useQueryClient } from '@tanstack/react-query';
import { emit } from '@tauri-apps/api/event'; import { emit } from '@tauri-apps/api/event';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
import type { AnyModel } from '@yaakapp-internal/models'; import type { AnyModel } from '@yaakapp-internal/models';
import type { ShowPromptRequest, ShowPromptResponse } from '@yaakapp-internal/plugin'; import type { PromptTextRequest, PromptTextResponse } from '@yaakapp-internal/plugin';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { useEnsureActiveCookieJar, useMigrateActiveCookieJarId } from '../hooks/useActiveCookieJar'; import { useEnsureActiveCookieJar, useMigrateActiveCookieJarId } from '../hooks/useActiveCookieJar';
@@ -180,11 +180,11 @@ export function GlobalHooks() {
useListenToTauriEvent('zoom_reset', zoom.zoomReset); useListenToTauriEvent('zoom_reset', zoom.zoomReset);
const prompt = usePrompt(); const prompt = usePrompt();
useListenToTauriEvent<{ replyId: string; args: ShowPromptRequest }>( useListenToTauriEvent<{ replyId: string; args: PromptTextRequest }>(
'show_prompt', 'show_prompt',
async (event) => { async (event) => {
const value = await prompt(event.payload.args); const value = await prompt(event.payload.args);
const result: ShowPromptResponse = { value }; const result: PromptTextResponse = { value };
await emit(event.payload.replyId, result); await emit(event.payload.replyId, result);
}, },
); );

View File

@@ -49,6 +49,7 @@ export const RequestMethodDropdown = memo(function RequestMethodDropdown({
description: 'Enter a custom method name', description: 'Enter a custom method name',
placeholder: 'CUSTOM', placeholder: 'CUSTOM',
}); });
if (newMethod == null) return;
onChange(newMethod); onChange(newMethod);
}, },
}, },

View File

@@ -767,6 +767,7 @@ function SidebarItem({
placeholder: 'New Name', placeholder: 'New Name',
defaultValue: itemName, defaultValue: itemName,
}); });
if (name == null) return;
updateAnyFolder.mutate({ id: itemId, update: (f) => ({ ...f, name }) }); updateAnyFolder.mutate({ id: itemId, update: (f) => ({ ...f, name }) });
}, },
}, },

View File

@@ -2,11 +2,13 @@ import type {
TemplateFunction, TemplateFunction,
TemplateFunctionArg, TemplateFunctionArg,
TemplateFunctionCheckboxArg, TemplateFunctionCheckboxArg,
TemplateFunctionFileArg,
TemplateFunctionHttpRequestArg, TemplateFunctionHttpRequestArg,
TemplateFunctionSelectArg, TemplateFunctionSelectArg,
TemplateFunctionTextArg, TemplateFunctionTextArg,
} from '@yaakapp-internal/plugin'; } from '@yaakapp-internal/plugin';
import type { FnArg, Tokens } from '@yaakapp-internal/template'; import type { FnArg, Tokens } from '@yaakapp-internal/template';
import classNames from 'classnames';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useActiveRequest } from '../hooks/useActiveRequest'; import { useActiveRequest } from '../hooks/useActiveRequest';
import { useDebouncedValue } from '../hooks/useDebouncedValue'; import { useDebouncedValue } from '../hooks/useDebouncedValue';
@@ -20,6 +22,7 @@ import { InlineCode } from './core/InlineCode';
import { PlainInput } from './core/PlainInput'; import { PlainInput } from './core/PlainInput';
import { Select } from './core/Select'; import { Select } from './core/Select';
import { VStack } from './core/Stacks'; import { VStack } from './core/Stacks';
import { SelectFile } from './SelectFile';
const NULL_ARG = '__NULL__'; const NULL_ARG = '__NULL__';
@@ -50,8 +53,8 @@ export function TemplateFunctionDialog({ templateFunction, hide, initialTokens,
return initial; return initial;
}); });
const setArgValue = useCallback((name: string, value: string | boolean) => { const setArgValue = useCallback((name: string, value: string | boolean | null) => {
setArgValues((v) => ({ ...v, [name]: value })); setArgValues((v) => ({ ...v, [name]: value == null ? '__NULL__' : value }));
}, []); }, []);
const tokens: Tokens = useMemo(() => { const tokens: Tokens = useMemo(() => {
@@ -90,6 +93,7 @@ export function TemplateFunctionDialog({ templateFunction, hide, initialTokens,
const debouncedTagText = useDebouncedValue(tagText.data ?? '', 200); const debouncedTagText = useDebouncedValue(tagText.data ?? '', 200);
const rendered = useRenderTemplate(debouncedTagText); const rendered = useRenderTemplate(debouncedTagText);
const tooLarge = (rendered.data ?? '').length > 10000;
return ( return (
<VStack className="pb-3" space={4}> <VStack className="pb-3" space={4}>
@@ -133,10 +137,29 @@ export function TemplateFunctionDialog({ templateFunction, hide, initialTokens,
value={argValues[a.name] ? String(argValues[a.name]) : '__ERROR__'} value={argValues[a.name] ? String(argValues[a.name]) : '__ERROR__'}
/> />
); );
case 'file':
return (
<FileArg
key={i}
arg={a}
onChange={(v) => setArgValue(a.name, v)}
filePath={argValues[a.name] ? String(argValues[a.name]) : '__ERROR__'}
/>
);
} }
})} })}
</VStack> </VStack>
<InlineCode className="select-text cursor-text">{rendered.data || <>&nbsp;</>}</InlineCode> <VStack className="w-full">
<div className="text-sm text-text-subtle">Preview</div>
<InlineCode
className={classNames(
'whitespace-pre select-text cursor-text max-h-[10rem] overflow-y-auto hide-scrollbars',
tooLarge && 'italic text-danger',
)}
>
{tooLarge ? 'too large to preview' : rendered.data || <>&nbsp;</>}
</InlineCode>
</VStack>
<Button color="primary" onClick={handleDone}> <Button color="primary" onClick={handleDone}>
Done Done
</Button> </Button>
@@ -165,7 +188,13 @@ function TextArg({
name={arg.name} name={arg.name}
onChange={handleChange} onChange={handleChange}
defaultValue={value === NULL_ARG ? '' : value} defaultValue={value === NULL_ARG ? '' : value}
label={arg.label ?? arg.name} require={!arg.optional}
label={
<>
{arg.label ?? arg.name}
{arg.optional && <span> (optional)</span>}
</>
}
hideLabel={arg.label == null} hideLabel={arg.label == null}
placeholder={arg.placeholder ?? arg.defaultValue ?? ''} placeholder={arg.placeholder ?? arg.defaultValue ?? ''}
/> />
@@ -197,6 +226,24 @@ function SelectArg({
); );
} }
function FileArg({
arg,
filePath,
onChange,
}: {
arg: TemplateFunctionFileArg;
filePath: string;
onChange: (v: string | null) => void;
}) {
return (
<SelectFile
onChange={({ filePath }) => onChange(filePath)}
filePath={filePath === '__NULL__' ? null : filePath}
directory={!!arg.directory}
/>
);
}
function HttpRequestArg({ function HttpRequestArg({
arg, arg,
value, value,

View File

@@ -59,7 +59,7 @@ export function TemplateVariableDialog({ hide, onChange, initialTokens }: Props)
/> />
</VStack> </VStack>
<VStack> <VStack>
<div className="text-sm text-text-subtle">Render Preview</div> <div className="text-sm text-text-subtle">Preview</div>
<InlineCode className="select-text cursor-text">{rendered.data}</InlineCode> <InlineCode className="select-text cursor-text">{rendered.data}</InlineCode>
</VStack> </VStack>
<Button color="primary" onClick={handleDone}> <Button color="primary" onClick={handleDone}>

View File

@@ -66,6 +66,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
placeholder: 'New Name', placeholder: 'New Name',
defaultValue: activeWorkspace?.name, defaultValue: activeWorkspace?.name,
}); });
if (name == null) return;
updateWorkspace.mutate({ name }); updateWorkspace.mutate({ name });
}, },
}, },

View File

@@ -10,7 +10,7 @@ import { IconButton } from './IconButton';
export interface DialogProps { export interface DialogProps {
children: ReactNode; children: ReactNode;
open: boolean; open: boolean;
onClose: () => void; onClose?: () => void;
title?: ReactNode; title?: ReactNode;
description?: ReactNode; description?: ReactNode;
className?: string; className?: string;
@@ -44,7 +44,7 @@ export function Dialog({
'Escape', 'Escape',
() => { () => {
if (!open) return; if (!open) return;
onClose(); onClose?.();
}, },
{}, {},
[open], [open],

View File

@@ -26,7 +26,7 @@ export type InputProps = Omit<
> & { > & {
name?: string; name?: string;
type?: 'text' | 'password'; type?: 'text' | 'password';
label: string; label: ReactNode;
hideLabel?: boolean; hideLabel?: boolean;
labelPosition?: 'top' | 'left'; labelPosition?: 'top' | 'left';
labelClassName?: string; labelClassName?: string;

View File

@@ -512,7 +512,7 @@ function PairEditorRow({
leftSlot: <Icon icon="pencil" />, leftSlot: <Icon icon="pencil" />,
hidden: !pairContainer.pair.isFile, hidden: !pairContainer.pair.isFile,
onSelect: async () => { onSelect: async () => {
const v = await prompt({ const contentType = await prompt({
id: 'content-type', id: 'content-type',
require: false, require: false,
title: 'Override Content-Type', title: 'Override Content-Type',
@@ -522,7 +522,8 @@ function PairEditorRow({
confirmText: 'Set', confirmText: 'Set',
description: 'Leave blank to auto-detect', description: 'Leave blank to auto-detect',
}); });
handleChangeValueContentType(v); if (contentType == null) return;
handleChangeValueContentType(contentType);
}, },
}, },
{ {

View File

@@ -8,7 +8,7 @@ import { HStack } from '../components/core/Stacks';
export type PromptProps = Omit<ShowPromptRequest, 'id' | 'title' | 'description'> & { export type PromptProps = Omit<ShowPromptRequest, 'id' | 'title' | 'description'> & {
description?: ReactNode; description?: ReactNode;
onHide: () => void; onHide: () => void;
onResult: (value: string) => void; onResult: (value: string | null) => void;
}; };
export function Prompt({ export function Prompt({

View File

@@ -9,24 +9,30 @@ export function useCreateFolder() {
const workspace = useActiveWorkspace(); const workspace = useActiveWorkspace();
const prompt = usePrompt(); const prompt = usePrompt();
return useMutation<Folder, unknown, Partial<Pick<Folder, 'name' | 'sortPriority' | 'folderId'>>>({ return useMutation<void, unknown, Partial<Pick<Folder, 'name' | 'sortPriority' | 'folderId'>>>({
mutationKey: ['create_folder'], mutationKey: ['create_folder'],
mutationFn: async (patch) => { mutationFn: async (patch) => {
if (workspace === null) { if (workspace === null) {
throw new Error("Cannot create folder when there's no active workspace"); throw new Error("Cannot create folder when there's no active workspace");
} }
patch.name =
patch.name || if (!patch.name) {
(await prompt({ const name = await prompt({
id: 'new-folder', id: 'new-folder',
label: 'Name', label: 'Name',
defaultValue: 'Folder', defaultValue: 'Folder',
title: 'New Folder', title: 'New Folder',
confirmText: 'Create', confirmText: 'Create',
placeholder: 'Name', placeholder: 'Name',
})); });
if (name == null) {
return;
}
patch.name = name;
}
patch.sortPriority = patch.sortPriority || -Date.now(); patch.sortPriority = patch.sortPriority || -Date.now();
return invokeCmd('cmd_create_folder', { workspaceId: workspace.id, ...patch }); await invokeCmd('cmd_create_folder', { workspaceId: workspace.id, ...patch });
}, },
onSettled: () => trackEvent('folder', 'create'), onSettled: () => trackEvent('folder', 'create'),
}); });

View File

@@ -4,19 +4,27 @@ import type { PromptProps } from './Prompt';
import { Prompt } from './Prompt'; import { Prompt } from './Prompt';
type Props = Pick<DialogProps, 'title' | 'description'> & type Props = Pick<DialogProps, 'title' | 'description'> &
Omit<PromptProps, 'onResult' | 'onHide'> & { id: string }; Omit<PromptProps, 'onClose' | 'onHide' | 'onResult'> & { id: string };
export function usePrompt() { export function usePrompt() {
const dialog = useDialog(); const dialog = useDialog();
return ({ id, title, description, ...props }: Props) => return ({ id, title, description, ...props }: Props) =>
new Promise((onResult: PromptProps['onResult']) => { new Promise((resolve: PromptProps['onResult']) => {
dialog.show({ dialog.show({
id, id,
title, title,
description, description,
hideX: true, hideX: true,
size: 'sm', size: 'sm',
render: ({ hide: onHide }) => Prompt({ onHide, onResult, ...props }), render: ({ hide }) =>
Prompt({
onHide: () => {
hide();
resolve(null);
},
onResult: resolve,
...props,
}),
}); });
}); });
} }

View File

@@ -33,6 +33,9 @@ export function useRenameRequest(requestId: string | null) {
defaultValue: request.name, defaultValue: request.name,
confirmText: 'Save', confirmText: 'Save',
}); });
if (name == null) return;
if (request.model === 'http_request') { if (request.model === 'http_request') {
updateHttpRequest.mutate({ id: request.id, update: (r: HttpRequest) => ({ ...r, name }) }); updateHttpRequest.mutate({ id: request.id, update: (r: HttpRequest) => ({ ...r, name }) });
} else { } else {