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",
"app-build": "tauri build",
"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-plugins": "node scripts/vendor-plugins.cjs",
"bootstrap:vendor-protoc": "node scripts/vendor-protoc.cjs",

View File

@@ -1,12 +1,13 @@
{
"name": "@yaakapp/api",
"version": "0.2.13",
"version": "0.2.15",
"main": "lib/index.js",
"typings": "./lib/index.d.ts",
"files": [
"lib/**/*"
],
"scripts": {
"bootstrap": "npm run build",
"build": "run-s build:copy-types build:tsc",
"build:tsc": "tsc",
"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 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 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,
export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
/**
* Text to add to the confirmation button
*/
@@ -87,7 +81,17 @@ cancelText?: string,
*/
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, };
@@ -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 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, };

View File

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

View File

@@ -1,6 +1,7 @@
{
"name": "@yaakapp-internal/plugin-runtime",
"scripts": {
"bootstrap": "npm run build",
"build": "run-p build:*",
"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",

View File

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

View File

@@ -45,6 +45,7 @@ pub async fn send_http_request<R: Runtime>(
&WindowContext::from_window(window),
RenderPurpose::Send,
);
let rendered_request =
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,
GetHttpRequestActionsResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, Icon,
InternalEvent, InternalEventPayload, RenderHttpRequestResponse, RenderPurpose,
SendHttpRequestResponse, ShowPromptResponse, ShowToastRequest, TemplateRenderResponse,
SendHttpRequestResponse, PromptTextResponse, ShowToastRequest, TemplateRenderResponse,
WindowContext,
};
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.
request: HttpRequest,
) -> 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 {
Some(id) => match get_environment(&window, id).await {
Ok(env) => Some(env),
@@ -1101,18 +1113,6 @@ async fn cmd_send_http_request(
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(
&window,
&request,
@@ -2171,18 +2171,18 @@ async fn call_frontend<T: Serialize + Clone, R: Runtime>(
window: WebviewWindow<R>,
event_name: &str,
args: T,
) -> ShowPromptResponse {
) -> PromptTextResponse {
let reply_id = format!("{event_name}_reply");
let payload = FrontendCall {
args,
reply_id: reply_id.clone(),
};
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| {
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);
});
@@ -2220,11 +2220,11 @@ async fn handle_plugin_event<R: Runtime>(
};
None
}
InternalEventPayload::ShowPromptRequest(req) => {
InternalEventPayload::PromptTextRequest(req) => {
let window = get_window_from_window_context(app_handle, &window_context)
.expect("Failed to find window for render");
let resp = call_frontend(window, "show_prompt", req).await;
Some(InternalEventPayload::ShowPromptResponse(resp))
Some(InternalEventPayload::PromptTextResponse(resp))
}
InternalEventPayload::FindHttpResponsesRequest(req) => {
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 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 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,
export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
/**
* Text to add to the confirmation button
*/
@@ -87,7 +81,17 @@ cancelText?: string,
*/
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, };
@@ -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 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, };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,11 +2,13 @@ import type {
TemplateFunction,
TemplateFunctionArg,
TemplateFunctionCheckboxArg,
TemplateFunctionFileArg,
TemplateFunctionHttpRequestArg,
TemplateFunctionSelectArg,
TemplateFunctionTextArg,
} from '@yaakapp-internal/plugin';
import type { FnArg, Tokens } from '@yaakapp-internal/template';
import classNames from 'classnames';
import { useCallback, useMemo, useState } from 'react';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useDebouncedValue } from '../hooks/useDebouncedValue';
@@ -20,6 +22,7 @@ import { InlineCode } from './core/InlineCode';
import { PlainInput } from './core/PlainInput';
import { Select } from './core/Select';
import { VStack } from './core/Stacks';
import { SelectFile } from './SelectFile';
const NULL_ARG = '__NULL__';
@@ -50,8 +53,8 @@ export function TemplateFunctionDialog({ templateFunction, hide, initialTokens,
return initial;
});
const setArgValue = useCallback((name: string, value: string | boolean) => {
setArgValues((v) => ({ ...v, [name]: value }));
const setArgValue = useCallback((name: string, value: string | boolean | null) => {
setArgValues((v) => ({ ...v, [name]: value == null ? '__NULL__' : value }));
}, []);
const tokens: Tokens = useMemo(() => {
@@ -90,6 +93,7 @@ export function TemplateFunctionDialog({ templateFunction, hide, initialTokens,
const debouncedTagText = useDebouncedValue(tagText.data ?? '', 200);
const rendered = useRenderTemplate(debouncedTagText);
const tooLarge = (rendered.data ?? '').length > 10000;
return (
<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__'}
/>
);
case 'file':
return (
<FileArg
key={i}
arg={a}
onChange={(v) => setArgValue(a.name, v)}
filePath={argValues[a.name] ? String(argValues[a.name]) : '__ERROR__'}
/>
);
}
})}
</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}>
Done
</Button>
@@ -165,7 +188,13 @@ function TextArg({
name={arg.name}
onChange={handleChange}
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}
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({
arg,
value,

View File

@@ -59,7 +59,7 @@ export function TemplateVariableDialog({ hide, onChange, initialTokens }: Props)
/>
</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>
</VStack>
<Button color="primary" onClick={handleDone}>

View File

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

View File

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

View File

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

View File

@@ -512,7 +512,7 @@ function PairEditorRow({
leftSlot: <Icon icon="pencil" />,
hidden: !pairContainer.pair.isFile,
onSelect: async () => {
const v = await prompt({
const contentType = await prompt({
id: 'content-type',
require: false,
title: 'Override Content-Type',
@@ -522,7 +522,8 @@ function PairEditorRow({
confirmText: 'Set',
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'> & {
description?: ReactNode;
onHide: () => void;
onResult: (value: string) => void;
onResult: (value: string | null) => void;
};
export function Prompt({

View File

@@ -9,24 +9,30 @@ export function useCreateFolder() {
const workspace = useActiveWorkspace();
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'],
mutationFn: async (patch) => {
if (workspace === null) {
throw new Error("Cannot create folder when there's no active workspace");
}
patch.name =
patch.name ||
(await prompt({
if (!patch.name) {
const name = await prompt({
id: 'new-folder',
label: 'Name',
defaultValue: 'Folder',
title: 'New Folder',
confirmText: 'Create',
placeholder: 'Name',
}));
});
if (name == null) {
return;
}
patch.name = name;
}
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'),
});

View File

@@ -4,19 +4,27 @@ import type { PromptProps } from './Prompt';
import { Prompt } from './Prompt';
type Props = Pick<DialogProps, 'title' | 'description'> &
Omit<PromptProps, 'onResult' | 'onHide'> & { id: string };
Omit<PromptProps, 'onClose' | 'onHide' | 'onResult'> & { id: string };
export function usePrompt() {
const dialog = useDialog();
return ({ id, title, description, ...props }: Props) =>
new Promise((onResult: PromptProps['onResult']) => {
new Promise((resolve: PromptProps['onResult']) => {
dialog.show({
id,
title,
description,
hideX: true,
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,
confirmText: 'Save',
});
if (name == null) return;
if (request.model === 'http_request') {
updateHttpRequest.mutate({ id: request.id, update: (r: HttpRequest) => ({ ...r, name }) });
} else {