diff --git a/package.json b/package.json index 3a73a2fc..783fed0f 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/plugin-runtime-types/package.json b/plugin-runtime-types/package.json index 501e336d..e117e3c4 100644 --- a/plugin-runtime-types/package.json +++ b/plugin-runtime-types/package.json @@ -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:*", diff --git a/plugin-runtime-types/src/bindings/events.ts b/plugin-runtime-types/src/bindings/events.ts index 24dd4f75..709fa813 100644 --- a/plugin-runtime-types/src/bindings/events.ts +++ b/plugin-runtime-types/src/bindings/events.ts @@ -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, }; +export type OpenFileFilter = { name: string, +/** + * File extensions to require + */ +extensions: Array, }; -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, 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, 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, +/** + * 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, name: string, optional?: boolean, label?: string, defaultValue?: string, }; +export type TemplateFunctionSelectArg = { +/** + * The options that will be available in the select input + */ +options: Array, +/** + * 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, }; diff --git a/plugin-runtime-types/src/plugins/Context.ts b/plugin-runtime-types/src/plugins/Context.ts index 4f019322..0c1d897b 100644 --- a/plugin-runtime-types/src/plugins/Context.ts +++ b/plugin-runtime-types/src/plugins/Context.ts @@ -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; + text(args: PromptTextRequest): Promise; }; httpRequest: { send(args: SendHttpRequestRequest): Promise; diff --git a/plugin-runtime/package.json b/plugin-runtime/package.json index 82a88937..057d790d 100644 --- a/plugin-runtime/package.json +++ b/plugin-runtime/package.json @@ -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", diff --git a/plugin-runtime/src/index.worker.ts b/plugin-runtime/src/index.worker.ts index 9cb9c46b..0e76ac84 100644 --- a/plugin-runtime/src/index.worker.ts +++ b/plugin-runtime/src/index.worker.ts @@ -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; diff --git a/src-tauri/src/http_request.rs b/src-tauri/src/http_request.rs index 20973d08..5b103109 100644 --- a/src-tauri/src/http_request.rs +++ b/src-tauri/src/http_request.rs @@ -45,6 +45,7 @@ pub async fn send_http_request( &WindowContext::from_window(window), RenderPurpose::Send, ); + let rendered_request = render_http_request(&request, &workspace, environment.as_ref(), &cb).await; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 0493c3cc..f50b8cdf 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -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 { + 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( window: WebviewWindow, 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( }; 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( diff --git a/src-tauri/yaak_plugin_runtime/bindings/events.ts b/src-tauri/yaak_plugin_runtime/bindings/events.ts index 24dd4f75..709fa813 100644 --- a/src-tauri/yaak_plugin_runtime/bindings/events.ts +++ b/src-tauri/yaak_plugin_runtime/bindings/events.ts @@ -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, }; +export type OpenFileFilter = { name: string, +/** + * File extensions to require + */ +extensions: Array, }; -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, 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, 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, +/** + * 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, name: string, optional?: boolean, label?: string, defaultValue?: string, }; +export type TemplateFunctionSelectArg = { +/** + * The options that will be available in the select input + */ +options: Array, +/** + * 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, }; diff --git a/src-tauri/yaak_plugin_runtime/src/events.rs b/src-tauri/yaak_plugin_runtime/src/events.rs index 75cdf2fe..551ea6f0 100644 --- a/src-tauri/yaak_plugin_runtime/src/events.rs +++ b/src-tauri/yaak_plugin_runtime/src/events.rs @@ -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, } #[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>, pub args: Vec, @@ -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, + + /// The label of the input #[ts(optional)] pub label: Option, + + /// The default value #[ts(optional)] pub default_value: Option, } @@ -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, } @@ -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, + + // Select a directory, not a file #[ts(optional)] pub directory: Option, + + // Default file path for selection dialog #[ts(optional)] pub default_path: Option, + + // Specify to only allow selection of certain file extensions #[ts(optional)] pub filters: Option>, } @@ -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, } @@ -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, } @@ -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, } diff --git a/src-web/components/CookieDropdown.tsx b/src-web/components/CookieDropdown.tsx index 9448b416..c9843729 100644 --- a/src-web/components/CookieDropdown.tsx +++ b/src-web/components/CookieDropdown.tsx @@ -64,6 +64,7 @@ export function CookieDropdown() { placeholder: 'New name', defaultValue: activeCookieJar?.name, }); + if (name == null) return; updateCookieJar.mutate({ name }); }, }, diff --git a/src-web/components/DialogContext.tsx b/src-web/components/DialogContext.tsx index 970dd4ed..fbff736b 100644 --- a/src-web/components/DialogContext.tsx +++ b/src-web/components/DialogContext.tsx @@ -6,7 +6,7 @@ import { Dialog } from './core/Dialog'; type DialogEntry = { id: string; render: ({ hide }: { hide: () => void }) => React.ReactNode; -} & Omit; +} & Omit; interface State { dialogs: DialogEntry[]; diff --git a/src-web/components/EnvironmentEditDialog.tsx b/src-web/components/EnvironmentEditDialog.tsx index 7af3c72d..4fa75012 100644 --- a/src-web/components/EnvironmentEditDialog.tsx +++ b/src-web/components/EnvironmentEditDialog.tsx @@ -279,6 +279,7 @@ function SidebarButton({ placeholder: 'New Name', defaultValue: environment.name, }); + if (name == null) return; updateEnvironment.mutate({ name }); }, }, diff --git a/src-web/components/GlobalHooks.tsx b/src-web/components/GlobalHooks.tsx index d7bcaf9f..3d212524 100644 --- a/src-web/components/GlobalHooks.tsx +++ b/src-web/components/GlobalHooks.tsx @@ -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); }, ); diff --git a/src-web/components/RequestMethodDropdown.tsx b/src-web/components/RequestMethodDropdown.tsx index 61575622..3077575f 100644 --- a/src-web/components/RequestMethodDropdown.tsx +++ b/src-web/components/RequestMethodDropdown.tsx @@ -49,6 +49,7 @@ export const RequestMethodDropdown = memo(function RequestMethodDropdown({ description: 'Enter a custom method name', placeholder: 'CUSTOM', }); + if (newMethod == null) return; onChange(newMethod); }, }, diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx index 3582726e..98236f99 100644 --- a/src-web/components/Sidebar.tsx +++ b/src-web/components/Sidebar.tsx @@ -767,6 +767,7 @@ function SidebarItem({ placeholder: 'New Name', defaultValue: itemName, }); + if (name == null) return; updateAnyFolder.mutate({ id: itemId, update: (f) => ({ ...f, name }) }); }, }, diff --git a/src-web/components/TemplateFunctionDialog.tsx b/src-web/components/TemplateFunctionDialog.tsx index e6d287f3..9b40010e 100644 --- a/src-web/components/TemplateFunctionDialog.tsx +++ b/src-web/components/TemplateFunctionDialog.tsx @@ -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 ( @@ -133,10 +137,29 @@ export function TemplateFunctionDialog({ templateFunction, hide, initialTokens, value={argValues[a.name] ? String(argValues[a.name]) : '__ERROR__'} /> ); + case 'file': + return ( + setArgValue(a.name, v)} + filePath={argValues[a.name] ? String(argValues[a.name]) : '__ERROR__'} + /> + ); } })} - {rendered.data || <> } + +
Preview
+ + {tooLarge ? 'too large to preview' : rendered.data || <> } + +
@@ -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 && (optional)} + + } 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 ( + onChange(filePath)} + filePath={filePath === '__NULL__' ? null : filePath} + directory={!!arg.directory} + /> + ); +} + function HttpRequestArg({ arg, value, diff --git a/src-web/components/TemplateVariableDialog.tsx b/src-web/components/TemplateVariableDialog.tsx index 245ac999..aa370172 100644 --- a/src-web/components/TemplateVariableDialog.tsx +++ b/src-web/components/TemplateVariableDialog.tsx @@ -59,7 +59,7 @@ export function TemplateVariableDialog({ hide, onChange, initialTokens }: Props) /> -
Render Preview
+
Preview
{rendered.data}