mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-23 09:51:28 +01:00
Improved prompt function add add ctx.* functions (#301)
This commit is contained in:
21
package-lock.json
generated
21
package-lock.json
generated
@@ -30,6 +30,7 @@
|
||||
"plugins/importer-postman-environment",
|
||||
"plugins/importer-yaak",
|
||||
"plugins/template-function-cookie",
|
||||
"plugins/template-function-ctx",
|
||||
"plugins/template-function-encode",
|
||||
"plugins/template-function-fs",
|
||||
"plugins/template-function-hash",
|
||||
@@ -4344,6 +4345,10 @@
|
||||
"resolved": "plugins/template-function-cookie",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaak/template-function-ctx": {
|
||||
"resolved": "plugins/template-function-ctx",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaak/template-function-encode": {
|
||||
"resolved": "plugins/template-function-encode",
|
||||
"link": true
|
||||
@@ -18969,7 +18974,7 @@
|
||||
},
|
||||
"packages/plugin-runtime-types": {
|
||||
"name": "@yaakapp/api",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.1",
|
||||
"dependencies": {
|
||||
"@types/node": "^24.0.13"
|
||||
},
|
||||
@@ -19097,6 +19102,10 @@
|
||||
"name": "@yaak/template-function-cookie",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"plugins/template-function-ctx": {
|
||||
"name": "@yaak/template-function-ctx",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"plugins/template-function-datetime": {
|
||||
"version": "0.1.0",
|
||||
"extraneous": true,
|
||||
@@ -19128,7 +19137,10 @@
|
||||
},
|
||||
"plugins/template-function-prompt": {
|
||||
"name": "@yaak/template-function-prompt",
|
||||
"version": "0.1.0"
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"slugify": "^1.6.6"
|
||||
}
|
||||
},
|
||||
"plugins/template-function-random": {
|
||||
"name": "@yaak/template-function-random",
|
||||
@@ -19177,6 +19189,11 @@
|
||||
"uuid": "dist/esm/bin/uuid"
|
||||
}
|
||||
},
|
||||
"plugins/template-function-window": {
|
||||
"name": "@yaak/template-function-window",
|
||||
"version": "0.1.0",
|
||||
"extraneous": true
|
||||
},
|
||||
"plugins/template-function-xml": {
|
||||
"name": "@yaak/template-function-xml",
|
||||
"version": "0.1.0",
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"plugins/importer-postman-environment",
|
||||
"plugins/importer-yaak",
|
||||
"plugins/template-function-cookie",
|
||||
"plugins/template-function-ctx",
|
||||
"plugins/template-function-encode",
|
||||
"plugins/template-function-fs",
|
||||
"plugins/template-function-hash",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@yaakapp/api",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.1",
|
||||
"keywords": [
|
||||
"api-client",
|
||||
"insomnia-alternative",
|
||||
|
||||
@@ -224,7 +224,7 @@ defaultValue?: string, disabled?: boolean,
|
||||
*/
|
||||
description?: string, };
|
||||
|
||||
export type FormInputHStack = { inputs?: Array<FormInput>, };
|
||||
export type FormInputHStack = { inputs?: Array<FormInput>, hidden?: boolean, };
|
||||
|
||||
export type FormInputHttpRequest = {
|
||||
/**
|
||||
@@ -391,7 +391,7 @@ export type ImportResponse = { resources: ImportResources, };
|
||||
|
||||
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, context: PluginContext, payload: InternalEventPayload, };
|
||||
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } | { "type": "reload_response" } & ReloadResponse | { "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": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_function_summary_request" } & EmptyPayload | { "type": "get_template_function_summary_response" } & GetTemplateFunctionSummaryResponse | { "type": "get_template_function_config_request" } & GetTemplateFunctionConfigRequest | { "type": "get_template_function_config_response" } & GetTemplateFunctionConfigResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "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": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } | { "type": "reload_response" } & ReloadResponse | { "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": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_function_summary_request" } & EmptyPayload | { "type": "get_template_function_summary_response" } & GetTemplateFunctionSummaryResponse | { "type": "get_template_function_config_request" } & GetTemplateFunctionConfigRequest | { "type": "get_template_function_config_response" } & GetTemplateFunctionConfigResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "window_info_request" } & WindowInfoRequest | { "type": "window_info_response" } & WindowInfoResponse | { "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": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||
|
||||
export type JsonPrimitive = string | number | boolean | null;
|
||||
|
||||
@@ -411,7 +411,7 @@ export type PromptTextRequest = { id: string, title: string, label: string, desc
|
||||
/**
|
||||
* Text to add to the confirmation button
|
||||
*/
|
||||
confirmText?: string,
|
||||
confirmText?: string, password?: boolean,
|
||||
/**
|
||||
* Text to add to the cancel button
|
||||
*/
|
||||
@@ -445,7 +445,7 @@ export type SetKeyValueResponse = {};
|
||||
|
||||
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, timeout?: number, };
|
||||
|
||||
export type TemplateFunction = { name: string, description?: string,
|
||||
export type TemplateFunction = { name: string, previewType?: TemplateFunctionPreviewType, description?: string,
|
||||
/**
|
||||
* Also support alternative names. This is useful for not breaking existing
|
||||
* tags when changing the `name` property
|
||||
@@ -457,6 +457,8 @@ aliases?: Array<string>, args: Array<TemplateFunctionArg>, };
|
||||
*/
|
||||
export type TemplateFunctionArg = FormInput;
|
||||
|
||||
export type TemplateFunctionPreviewType = "live" | "click" | "none";
|
||||
|
||||
export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, };
|
||||
|
||||
export type TemplateRenderResponse = { data: JsonValue, };
|
||||
@@ -487,6 +489,10 @@ export type ThemeComponentColors = { surface?: string, surfaceHighlight?: string
|
||||
|
||||
export type ThemeComponents = { dialog?: ThemeComponentColors, menu?: ThemeComponentColors, toast?: ThemeComponentColors, sidebar?: ThemeComponentColors, responsePane?: ThemeComponentColors, appHeader?: ThemeComponentColors, button?: ThemeComponentColors, banner?: ThemeComponentColors, templateTag?: ThemeComponentColors, urlBar?: ThemeComponentColors, editor?: ThemeComponentColors, input?: ThemeComponentColors, };
|
||||
|
||||
export type WindowInfoRequest = { label: string, };
|
||||
|
||||
export type WindowInfoResponse = { requestId: string | null, environmentId: string | null, workspaceId: string | null, label: string, };
|
||||
|
||||
export type WindowNavigateEvent = { url: string, };
|
||||
|
||||
export type WindowSize = { width: number, height: number, };
|
||||
|
||||
@@ -18,7 +18,7 @@ import type {
|
||||
ShowToastRequest,
|
||||
TemplateRenderRequest,
|
||||
} from '../bindings/gen_events.ts';
|
||||
import { JsonValue } from '../bindings/serde_json/JsonValue';
|
||||
import type { JsonValue } from '../bindings/serde_json/JsonValue';
|
||||
|
||||
export interface Context {
|
||||
clipboard: {
|
||||
@@ -36,6 +36,9 @@ export interface Context {
|
||||
delete(key: string): Promise<boolean>;
|
||||
};
|
||||
window: {
|
||||
requestId(): Promise<string | null>;
|
||||
workspaceId(): Promise<string | null>;
|
||||
environmentId(): Promise<string | null>;
|
||||
openUrl(
|
||||
args: OpenWindowRequest & {
|
||||
onNavigate?: (args: { url: string }) => void;
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
SendHttpRequestResponse,
|
||||
TemplateFunction,
|
||||
TemplateRenderResponse,
|
||||
WindowInfoResponse,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import { Context, PluginDefinition } from '@yaakapp/api';
|
||||
import console from 'node:console';
|
||||
@@ -317,7 +318,21 @@ export class PluginInstance {
|
||||
Array.isArray(this.#mod?.templateFunctions)
|
||||
) {
|
||||
const fn = this.#mod.templateFunctions.find((a) => a.name === payload.name);
|
||||
if (typeof fn?.onRender === 'function') {
|
||||
if (
|
||||
payload.args.purpose === 'preview' &&
|
||||
(fn?.previewType === 'click' || fn?.previewType === 'none')
|
||||
) {
|
||||
// Send empty render response
|
||||
this.#sendPayload(
|
||||
context,
|
||||
{
|
||||
type: 'call_template_function_response',
|
||||
value: null,
|
||||
error: 'Live preview disabled for this function',
|
||||
},
|
||||
replyId,
|
||||
);
|
||||
} else if (typeof fn?.onRender === 'function') {
|
||||
const resolvedArgs = await applyDynamicFormInput(ctx, fn.args, payload.args);
|
||||
payload.args.values = applyFormInputDefaults(resolvedArgs, payload.args.values);
|
||||
try {
|
||||
@@ -410,7 +425,7 @@ export class PluginInstance {
|
||||
return this.#sendPayload(context, { type: 'empty_response' }, replyId);
|
||||
}
|
||||
|
||||
#sendAndWaitForReply<T extends Omit<InternalEventPayload, 'type'>>(
|
||||
#sendForReply<T extends Omit<InternalEventPayload, 'type'>>(
|
||||
context: PluginContext,
|
||||
payload: InternalEventPayload,
|
||||
): Promise<T> {
|
||||
@@ -456,10 +471,22 @@ export class PluginInstance {
|
||||
}
|
||||
|
||||
#newCtx(context: PluginContext): Context {
|
||||
const _windowInfo = async () => {
|
||||
if (context.label == null) {
|
||||
throw new Error("Can't get window context without an active window");
|
||||
}
|
||||
const payload: InternalEventPayload = {
|
||||
type: 'window_info_request',
|
||||
label: context.label,
|
||||
};
|
||||
|
||||
return this.#sendForReply<WindowInfoResponse>(context, payload);
|
||||
};
|
||||
|
||||
return {
|
||||
clipboard: {
|
||||
copyText: async (text) => {
|
||||
await this.#sendAndWaitForReply(context, {
|
||||
await this.#sendForReply(context, {
|
||||
type: 'copy_text_request',
|
||||
text,
|
||||
});
|
||||
@@ -467,7 +494,7 @@ export class PluginInstance {
|
||||
},
|
||||
toast: {
|
||||
show: async (args) => {
|
||||
await this.#sendAndWaitForReply(context, {
|
||||
await this.#sendForReply(context, {
|
||||
type: 'show_toast_request',
|
||||
// Handle default here because null/undefined both convert to None in Rust translation
|
||||
timeout: args.timeout === undefined ? 5000 : args.timeout,
|
||||
@@ -476,6 +503,15 @@ export class PluginInstance {
|
||||
},
|
||||
},
|
||||
window: {
|
||||
requestId: async () => {
|
||||
return (await _windowInfo()).requestId;
|
||||
},
|
||||
async workspaceId(): Promise<string | null> {
|
||||
return (await _windowInfo()).workspaceId;
|
||||
},
|
||||
async environmentId(): Promise<string | null> {
|
||||
return (await _windowInfo()).environmentId;
|
||||
},
|
||||
openUrl: async ({ onNavigate, onClose, ...args }) => {
|
||||
args.label = args.label || `${Math.random()}`;
|
||||
const payload: InternalEventPayload = { type: 'open_window_request', ...args };
|
||||
@@ -500,7 +536,7 @@ export class PluginInstance {
|
||||
},
|
||||
prompt: {
|
||||
text: async (args) => {
|
||||
const reply: PromptTextResponse = await this.#sendAndWaitForReply(context, {
|
||||
const reply: PromptTextResponse = await this.#sendForReply(context, {
|
||||
type: 'prompt_text_request',
|
||||
...args,
|
||||
});
|
||||
@@ -513,7 +549,7 @@ export class PluginInstance {
|
||||
type: 'find_http_responses_request',
|
||||
...args,
|
||||
} as const;
|
||||
const { httpResponses } = await this.#sendAndWaitForReply<FindHttpResponsesResponse>(
|
||||
const { httpResponses } = await this.#sendForReply<FindHttpResponsesResponse>(
|
||||
context,
|
||||
payload,
|
||||
);
|
||||
@@ -526,7 +562,7 @@ export class PluginInstance {
|
||||
type: 'render_grpc_request_request',
|
||||
...args,
|
||||
} as const;
|
||||
const { grpcRequest } = await this.#sendAndWaitForReply<RenderGrpcRequestResponse>(
|
||||
const { grpcRequest } = await this.#sendForReply<RenderGrpcRequestResponse>(
|
||||
context,
|
||||
payload,
|
||||
);
|
||||
@@ -539,7 +575,7 @@ export class PluginInstance {
|
||||
type: 'get_http_request_by_id_request',
|
||||
...args,
|
||||
} as const;
|
||||
const { httpRequest } = await this.#sendAndWaitForReply<GetHttpRequestByIdResponse>(
|
||||
const { httpRequest } = await this.#sendForReply<GetHttpRequestByIdResponse>(
|
||||
context,
|
||||
payload,
|
||||
);
|
||||
@@ -550,7 +586,7 @@ export class PluginInstance {
|
||||
type: 'send_http_request_request',
|
||||
...args,
|
||||
} as const;
|
||||
const { httpResponse } = await this.#sendAndWaitForReply<SendHttpRequestResponse>(
|
||||
const { httpResponse } = await this.#sendForReply<SendHttpRequestResponse>(
|
||||
context,
|
||||
payload,
|
||||
);
|
||||
@@ -561,7 +597,7 @@ export class PluginInstance {
|
||||
type: 'render_http_request_request',
|
||||
...args,
|
||||
} as const;
|
||||
const { httpRequest } = await this.#sendAndWaitForReply<RenderHttpRequestResponse>(
|
||||
const { httpRequest } = await this.#sendForReply<RenderHttpRequestResponse>(
|
||||
context,
|
||||
payload,
|
||||
);
|
||||
@@ -574,18 +610,12 @@ export class PluginInstance {
|
||||
type: 'get_cookie_value_request',
|
||||
...args,
|
||||
} as const;
|
||||
const { value } = await this.#sendAndWaitForReply<GetCookieValueResponse>(
|
||||
context,
|
||||
payload,
|
||||
);
|
||||
const { value } = await this.#sendForReply<GetCookieValueResponse>(context, payload);
|
||||
return value;
|
||||
},
|
||||
listNames: async () => {
|
||||
const payload = { type: 'list_cookie_names_request' } as const;
|
||||
const { names } = await this.#sendAndWaitForReply<ListCookieNamesResponse>(
|
||||
context,
|
||||
payload,
|
||||
);
|
||||
const { names } = await this.#sendForReply<ListCookieNamesResponse>(context, payload);
|
||||
return names;
|
||||
},
|
||||
},
|
||||
@@ -596,14 +626,14 @@ export class PluginInstance {
|
||||
*/
|
||||
render: async (args) => {
|
||||
const payload = { type: 'template_render_request', ...args } as const;
|
||||
const result = await this.#sendAndWaitForReply<TemplateRenderResponse>(context, payload);
|
||||
const result = await this.#sendForReply<TemplateRenderResponse>(context, payload);
|
||||
return result.data as any;
|
||||
},
|
||||
},
|
||||
store: {
|
||||
get: async <T>(key: string) => {
|
||||
const payload = { type: 'get_key_value_request', key } as const;
|
||||
const result = await this.#sendAndWaitForReply<GetKeyValueResponse>(context, payload);
|
||||
const result = await this.#sendForReply<GetKeyValueResponse>(context, payload);
|
||||
return result.value ? (JSON.parse(result.value) as T) : undefined;
|
||||
},
|
||||
set: async <T>(key: string, value: T) => {
|
||||
@@ -613,11 +643,11 @@ export class PluginInstance {
|
||||
key,
|
||||
value: valueStr,
|
||||
};
|
||||
await this.#sendAndWaitForReply<GetKeyValueResponse>(context, payload);
|
||||
await this.#sendForReply<GetKeyValueResponse>(context, payload);
|
||||
},
|
||||
delete: async (key: string) => {
|
||||
const payload = { type: 'delete_key_value_request', key } as const;
|
||||
const result = await this.#sendAndWaitForReply<DeleteKeyValueResponse>(context, payload);
|
||||
const result = await this.#sendForReply<DeleteKeyValueResponse>(context, payload);
|
||||
return result.deleted;
|
||||
},
|
||||
},
|
||||
|
||||
12
plugins/template-function-ctx/package.json
Normal file
12
plugins/template-function-ctx/package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "@yaak/template-function-ctx",
|
||||
"displayName": "Window Template Functions",
|
||||
"description": "Template functions for accessing attributes of the current window",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
}
|
||||
}
|
||||
30
plugins/template-function-ctx/src/index.ts
Normal file
30
plugins/template-function-ctx/src/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import type { PluginDefinition } from '@yaakapp/api';
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
templateFunctions: [
|
||||
{
|
||||
name: 'ctx.request',
|
||||
description: 'Get the ID of the currently active request',
|
||||
args: [],
|
||||
async onRender(ctx) {
|
||||
return ctx.window.requestId();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ctx.environment',
|
||||
description: 'Get the ID of the currently active environment',
|
||||
args: [],
|
||||
async onRender(ctx) {
|
||||
return ctx.window.environmentId();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ctx.workspace',
|
||||
description: 'Get the ID of the currently active workspace',
|
||||
args: [],
|
||||
async onRender(ctx) {
|
||||
return ctx.window.workspaceId();
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
3
plugins/template-function-ctx/tsconfig.json
Normal file
3
plugins/template-function-ctx/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json"
|
||||
}
|
||||
@@ -58,7 +58,6 @@ export const plugin: PluginDefinition = {
|
||||
],
|
||||
async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||
try {
|
||||
console.log('formatted', args.values.formatted);
|
||||
return filterJSONPath(
|
||||
String(args.values.input),
|
||||
String(args.values.query),
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"scripts": {
|
||||
"build": "yaakcli build",
|
||||
"dev": "yaakcli dev",
|
||||
"lint":"tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
},
|
||||
"dependencies": {
|
||||
"slugify": "^1.6.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,174 @@
|
||||
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
|
||||
import slugify from 'slugify';
|
||||
|
||||
const STORE_NONE = 'none';
|
||||
const STORE_FOREVER = 'forever';
|
||||
const STORE_EXPIRE = 'expire';
|
||||
|
||||
interface Saved {
|
||||
value: string;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
export const plugin: PluginDefinition = {
|
||||
templateFunctions: [{
|
||||
name: 'prompt.text',
|
||||
description: 'Prompt the user for input when sending a request',
|
||||
args: [
|
||||
{ type: 'text', name: 'title', label: 'Title' },
|
||||
{ type: 'text', name: 'label', label: 'Label', optional: true },
|
||||
{ type: 'text', name: 'defaultValue', label: 'Default Value', optional: true },
|
||||
{ type: 'text', name: 'placeholder', label: 'Placeholder', optional: true },
|
||||
],
|
||||
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||
if (args.purpose !== 'send') return null;
|
||||
templateFunctions: [
|
||||
{
|
||||
name: 'prompt.text',
|
||||
description: 'Prompt the user for input when sending a request',
|
||||
previewType: 'click',
|
||||
args: [
|
||||
{ type: 'text', name: 'label', label: 'Label' },
|
||||
{
|
||||
type: 'select',
|
||||
name: 'store',
|
||||
label: 'Store Input',
|
||||
defaultValue: STORE_NONE,
|
||||
options: [
|
||||
{ label: 'Never', value: STORE_NONE },
|
||||
{ label: 'Expire', value: STORE_EXPIRE },
|
||||
{ label: 'Forever', value: STORE_FOREVER },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'h_stack',
|
||||
dynamic(_ctx, args) {
|
||||
return { hidden: args.values.store === STORE_NONE };
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'namespace',
|
||||
label: 'Namespace',
|
||||
defaultValue: '${[ctx.workspace()]}',
|
||||
optional: true,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'key',
|
||||
label: 'Key (defaults to Label)',
|
||||
optional: true,
|
||||
dynamic(_ctx, args) {
|
||||
return { placeholder: String(args.values.label || '') };
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'ttl',
|
||||
label: 'TTL (seconds)',
|
||||
placeholder: '0',
|
||||
defaultValue: '0',
|
||||
optional: true,
|
||||
dynamic(_ctx, args) {
|
||||
return { hidden: args.values.store !== STORE_EXPIRE };
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'banner',
|
||||
color: 'info',
|
||||
dynamic(_ctx, args) {
|
||||
return { hidden: args.values.store === STORE_NONE };
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
type: 'markdown',
|
||||
content: '',
|
||||
async dynamic(_ctx, args) {
|
||||
const key = buildKey(args);
|
||||
return {
|
||||
content: ['Value will be saved under: `' + key + '`'].join('\n\n'),
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'accordion',
|
||||
label: 'Advanced',
|
||||
inputs: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'title',
|
||||
label: 'Prompt Title',
|
||||
optional: true,
|
||||
placeholder: 'Enter Value',
|
||||
},
|
||||
{ type: 'text', name: 'defaultValue', label: 'Default Value', optional: true },
|
||||
{ type: 'text', name: 'placeholder', label: 'Input Placeholder', optional: true },
|
||||
{ type: 'checkbox', name: 'password', label: 'Mask Value' },
|
||||
],
|
||||
},
|
||||
],
|
||||
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||
if (args.purpose !== 'send') return null;
|
||||
|
||||
return await ctx.prompt.text({
|
||||
id: `prompt-${args.values.label}`,
|
||||
label: String(args.values.title ?? ''),
|
||||
title: String(args.values.title ?? ''),
|
||||
defaultValue: String(args.values.defaultValue),
|
||||
placeholder: String(args.values.placeholder),
|
||||
});
|
||||
if (args.values.store !== STORE_NONE && !args.values.namespace) {
|
||||
throw new Error('Namespace is required when storing values')
|
||||
}
|
||||
|
||||
const existing = await maybeGetValue(ctx, args);
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
const value = await ctx.prompt.text({
|
||||
id: `prompt-${args.values.label ?? 'none'}`,
|
||||
label: String(args.values.label || 'Value'),
|
||||
title: String(args.values.title ?? 'Enter Value'),
|
||||
defaultValue: String(args.values.defaultValue ?? ''),
|
||||
placeholder: String(args.values.placeholder ?? ''),
|
||||
password: Boolean(args.values.password),
|
||||
required: false,
|
||||
});
|
||||
|
||||
if (value == null) {
|
||||
throw new Error('Prompt cancelled');
|
||||
}
|
||||
|
||||
if (args.values.store !== STORE_NONE) {
|
||||
await maybeSetValue(ctx, args, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
},
|
||||
}],
|
||||
],
|
||||
};
|
||||
|
||||
function buildKey(args: CallTemplateFunctionArgs) {
|
||||
return [args.values.namespace, args.values.key || args.values.label]
|
||||
.filter((v) => !!v)
|
||||
.map((v) => slugify(String(v), { lower: true, trim: true }))
|
||||
.join('.');
|
||||
}
|
||||
|
||||
async function maybeGetValue(ctx: Context, args: CallTemplateFunctionArgs) {
|
||||
if (args.values.store === STORE_NONE) return null;
|
||||
|
||||
const existing = await ctx.store.get<Saved>(buildKey(args));
|
||||
if (existing == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (args.values.store === STORE_FOREVER) {
|
||||
return existing.value;
|
||||
}
|
||||
|
||||
const ttlSeconds = parseInt(String(args.values.ttl)) || 0;
|
||||
const ageSeconds = (Date.now() - existing.createdAt) / 1000;
|
||||
if (ageSeconds > ttlSeconds) {
|
||||
ctx.store.delete(buildKey(args)).catch(console.error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return existing.value;
|
||||
}
|
||||
|
||||
async function maybeSetValue(ctx: Context, args: CallTemplateFunctionArgs, value: string) {
|
||||
if (args.values.store === STORE_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.store.set<Saved>(buildKey(args), { value, createdAt: Date.now() });
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@ async fn cmd_render_template<R: Runtime>(
|
||||
template: &str,
|
||||
workspace_id: &str,
|
||||
environment_id: Option<&str>,
|
||||
purpose: Option<RenderPurpose>,
|
||||
) -> YaakResult<String> {
|
||||
let environment_chain =
|
||||
app_handle.db().resolve_environments(workspace_id, None, environment_id)?;
|
||||
@@ -126,7 +127,7 @@ async fn cmd_render_template<R: Runtime>(
|
||||
&PluginTemplateCallback::new(
|
||||
&app_handle,
|
||||
&PluginContext::new(&window),
|
||||
RenderPurpose::Preview,
|
||||
purpose.unwrap_or(RenderPurpose::Preview),
|
||||
),
|
||||
&RenderOptions {
|
||||
error_behavior: RenderErrorBehavior::Throw,
|
||||
|
||||
@@ -16,12 +16,13 @@ use yaak_models::models::{HttpResponse, Plugin};
|
||||
use yaak_models::queries::any_request::AnyRequest;
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
use yaak_models::util::UpdateSource;
|
||||
use yaak_plugins::error::Error::PluginErr;
|
||||
use yaak_plugins::events::{
|
||||
Color, DeleteKeyValueResponse, EmptyPayload, ErrorResponse, FindHttpResponsesResponse,
|
||||
GetCookieValueResponse, GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent,
|
||||
InternalEventPayload, ListCookieNamesResponse, RenderGrpcRequestResponse,
|
||||
RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest,
|
||||
TemplateRenderResponse, WindowNavigateEvent,
|
||||
TemplateRenderResponse, WindowInfoResponse, WindowNavigateEvent,
|
||||
};
|
||||
use yaak_plugins::plugin_handle::PluginHandle;
|
||||
use yaak_plugins::template_callback::PluginTemplateCallback;
|
||||
@@ -334,6 +335,29 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
||||
};
|
||||
Ok(Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value })))
|
||||
}
|
||||
InternalEventPayload::WindowInfoRequest(req) => {
|
||||
let w = app_handle
|
||||
.get_webview_window(&req.label)
|
||||
.ok_or(PluginErr(format!("Failed to find window for {}", req.label)))?;
|
||||
|
||||
// Actually look up the data so we never return an invalid ID
|
||||
let environment_id = environment_from_window(&w).map(|m| m.id);
|
||||
let workspace_id = workspace_from_window(&w).map(|m| m.id);
|
||||
let request_id =
|
||||
match app_handle.db().get_any_request(&w.request_id().unwrap_or_default()) {
|
||||
Ok(AnyRequest::HttpRequest(r)) => Some(r.id),
|
||||
Ok(AnyRequest::WebsocketRequest(r)) => Some(r.id),
|
||||
Ok(AnyRequest::GrpcRequest(r)) => Some(r.id),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
Ok(Some(InternalEventPayload::WindowInfoResponse(WindowInfoResponse {
|
||||
label: w.label().to_string(),
|
||||
request_id,
|
||||
workspace_id,
|
||||
environment_id,
|
||||
})))
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ defaultValue?: string, disabled?: boolean,
|
||||
*/
|
||||
description?: string, };
|
||||
|
||||
export type FormInputHStack = { inputs?: Array<FormInput>, };
|
||||
export type FormInputHStack = { inputs?: Array<FormInput>, hidden?: boolean, };
|
||||
|
||||
export type FormInputHttpRequest = {
|
||||
/**
|
||||
@@ -391,7 +391,7 @@ export type ImportResponse = { resources: ImportResources, };
|
||||
|
||||
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, context: PluginContext, payload: InternalEventPayload, };
|
||||
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } | { "type": "reload_response" } & ReloadResponse | { "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": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_function_summary_request" } & EmptyPayload | { "type": "get_template_function_summary_response" } & GetTemplateFunctionSummaryResponse | { "type": "get_template_function_config_request" } & GetTemplateFunctionConfigRequest | { "type": "get_template_function_config_response" } & GetTemplateFunctionConfigResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "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": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } | { "type": "reload_response" } & ReloadResponse | { "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": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_function_summary_request" } & EmptyPayload | { "type": "get_template_function_summary_response" } & GetTemplateFunctionSummaryResponse | { "type": "get_template_function_config_request" } & GetTemplateFunctionConfigRequest | { "type": "get_template_function_config_response" } & GetTemplateFunctionConfigResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "window_info_request" } & WindowInfoRequest | { "type": "window_info_response" } & WindowInfoResponse | { "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": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||
|
||||
export type JsonPrimitive = string | number | boolean | null;
|
||||
|
||||
@@ -411,7 +411,7 @@ export type PromptTextRequest = { id: string, title: string, label: string, desc
|
||||
/**
|
||||
* Text to add to the confirmation button
|
||||
*/
|
||||
confirmText?: string,
|
||||
confirmText?: string, password?: boolean,
|
||||
/**
|
||||
* Text to add to the cancel button
|
||||
*/
|
||||
@@ -445,7 +445,7 @@ export type SetKeyValueResponse = {};
|
||||
|
||||
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, timeout?: number, };
|
||||
|
||||
export type TemplateFunction = { name: string, description?: string,
|
||||
export type TemplateFunction = { name: string, previewType?: TemplateFunctionPreviewType, description?: string,
|
||||
/**
|
||||
* Also support alternative names. This is useful for not breaking existing
|
||||
* tags when changing the `name` property
|
||||
@@ -457,6 +457,8 @@ aliases?: Array<string>, args: Array<TemplateFunctionArg>, };
|
||||
*/
|
||||
export type TemplateFunctionArg = FormInput;
|
||||
|
||||
export type TemplateFunctionPreviewType = "live" | "click" | "none";
|
||||
|
||||
export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, };
|
||||
|
||||
export type TemplateRenderResponse = { data: JsonValue, };
|
||||
@@ -487,6 +489,10 @@ export type ThemeComponentColors = { surface?: string, surfaceHighlight?: string
|
||||
|
||||
export type ThemeComponents = { dialog?: ThemeComponentColors, menu?: ThemeComponentColors, toast?: ThemeComponentColors, sidebar?: ThemeComponentColors, responsePane?: ThemeComponentColors, appHeader?: ThemeComponentColors, button?: ThemeComponentColors, banner?: ThemeComponentColors, templateTag?: ThemeComponentColors, urlBar?: ThemeComponentColors, editor?: ThemeComponentColors, input?: ThemeComponentColors, };
|
||||
|
||||
export type WindowInfoRequest = { label: string, };
|
||||
|
||||
export type WindowInfoResponse = { requestId: string | null, environmentId: string | null, workspaceId: string | null, label: string, };
|
||||
|
||||
export type WindowNavigateEvent = { url: string, };
|
||||
|
||||
export type WindowSize = { width: number, height: number, };
|
||||
|
||||
@@ -147,6 +147,9 @@ pub enum InternalEventPayload {
|
||||
PromptTextRequest(PromptTextRequest),
|
||||
PromptTextResponse(PromptTextResponse),
|
||||
|
||||
WindowInfoRequest(WindowInfoRequest),
|
||||
WindowInfoResponse(WindowInfoResponse),
|
||||
|
||||
GetHttpRequestByIdRequest(GetHttpRequestByIdRequest),
|
||||
GetHttpRequestByIdResponse(GetHttpRequestByIdResponse),
|
||||
|
||||
@@ -521,6 +524,8 @@ pub struct PromptTextRequest {
|
||||
/// Text to add to the confirmation button
|
||||
#[ts(optional)]
|
||||
pub confirm_text: Option<String>,
|
||||
#[ts(optional)]
|
||||
pub password: Option<bool>,
|
||||
/// Text to add to the cancel button
|
||||
#[ts(optional)]
|
||||
pub cancel_text: Option<String>,
|
||||
@@ -536,6 +541,23 @@ pub struct PromptTextResponse {
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct WindowInfoRequest {
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct WindowInfoResponse {
|
||||
pub request_id: Option<String>,
|
||||
pub environment_id: Option<String>,
|
||||
pub workspace_id: Option<String>,
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
@@ -710,12 +732,24 @@ pub struct GetTemplateFunctionConfigResponse {
|
||||
pub plugin_ref_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub enum TemplateFunctionPreviewType {
|
||||
Live,
|
||||
Click,
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct TemplateFunction {
|
||||
pub name: String,
|
||||
|
||||
#[ts(optional)]
|
||||
pub preview_type: Option<TemplateFunctionPreviewType>,
|
||||
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
|
||||
@@ -972,6 +1006,9 @@ pub struct FormInputAccordion {
|
||||
pub struct FormInputHStack {
|
||||
#[ts(optional)]
|
||||
pub inputs: Option<Vec<FormInput>>,
|
||||
|
||||
#[ts(optional)]
|
||||
pub hidden: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::events::{
|
||||
FormInput, FormInputBase, FormInputText, PluginContext, RenderPurpose, TemplateFunction,
|
||||
TemplateFunctionArg,
|
||||
TemplateFunctionArg, TemplateFunctionPreviewType,
|
||||
};
|
||||
use crate::template_callback::PluginTemplateCallback;
|
||||
use base64::Engine;
|
||||
@@ -17,6 +17,7 @@ use yaak_templates::{FnArg, Parser, Token, Tokens, Val, transform_args};
|
||||
pub(crate) fn template_function_secure() -> TemplateFunction {
|
||||
TemplateFunction {
|
||||
name: "secure".to_string(),
|
||||
preview_type: Some(TemplateFunctionPreviewType::None),
|
||||
description: Some("Securely store encrypted text".to_string()),
|
||||
aliases: None,
|
||||
args: vec![TemplateFunctionArg::FormInput(FormInput::Text(
|
||||
@@ -37,6 +38,7 @@ pub(crate) fn template_function_secure() -> TemplateFunction {
|
||||
pub(crate) fn template_function_keyring() -> TemplateFunction {
|
||||
TemplateFunction {
|
||||
name: "keychain".to_string(),
|
||||
preview_type: Some(TemplateFunctionPreviewType::Live),
|
||||
description: Some("Get a password from the OS keychain or keyring".to_string()),
|
||||
aliases: Some(vec!["keyring".to_string()]),
|
||||
args: vec![
|
||||
|
||||
@@ -30,7 +30,6 @@ export const createFolder = createFastMutation<
|
||||
label: 'Name',
|
||||
defaultValue: 'Folder',
|
||||
title: 'New Folder',
|
||||
required: true,
|
||||
confirmText: 'Create',
|
||||
placeholder: 'Name',
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ import { IconButton } from './core/IconButton';
|
||||
import { Input } from './core/Input';
|
||||
import { Label } from './core/Label';
|
||||
import { Select } from './core/Select';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { Markdown } from './Markdown';
|
||||
import { SelectFile } from './SelectFile';
|
||||
|
||||
@@ -216,7 +216,7 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
);
|
||||
case 'h_stack':
|
||||
return (
|
||||
<HStack key={i + stateKey} alignItems="end" space={3}>
|
||||
<div className="flex flex-wrap sm:flex-nowrap gap-3 items-end" key={i + stateKey}>
|
||||
<FormInputs
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
@@ -226,7 +226,7 @@ function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
autocompleteFunctions={autocompleteFunctions || false}
|
||||
autocompleteVariables={autocompleteVariables}
|
||||
/>
|
||||
</HStack>
|
||||
</div>
|
||||
);
|
||||
case 'banner':
|
||||
return (
|
||||
|
||||
@@ -50,7 +50,6 @@ export const RequestMethodDropdown = memo(function RequestMethodDropdown({
|
||||
const newMethod = await showPrompt({
|
||||
id: 'custom-method',
|
||||
label: 'Http Method',
|
||||
defaultValue: '',
|
||||
title: 'Custom Method',
|
||||
confirmText: 'Save',
|
||||
description: 'Enter a custom method name',
|
||||
|
||||
@@ -54,7 +54,6 @@ export function TemplateFunctionDialog({ initialTokens, templateFunction, ...pro
|
||||
initial.value = await convertTemplateToInsecure(template);
|
||||
}
|
||||
|
||||
console.log('INITIAL', initial);
|
||||
setInitialArgValues(initial);
|
||||
})().catch(console.error);
|
||||
}, [
|
||||
@@ -78,7 +77,7 @@ export function TemplateFunctionDialog({ initialTokens, templateFunction, ...pro
|
||||
}
|
||||
|
||||
function InitializedTemplateFunctionDialog({
|
||||
templateFunction: { name },
|
||||
templateFunction: { name, previewType: ogPreviewType },
|
||||
initialArgValues,
|
||||
hide,
|
||||
onChange,
|
||||
@@ -86,7 +85,7 @@ function InitializedTemplateFunctionDialog({
|
||||
}: Omit<Props, 'initialTokens'> & {
|
||||
initialArgValues: Record<string, string | boolean>;
|
||||
}) {
|
||||
const enablePreview = name !== 'secure';
|
||||
const previewType = ogPreviewType == null ? 'live' : ogPreviewType;
|
||||
const [showSecretsInPreview, toggleShowSecretsInPreview] = useToggle(false);
|
||||
const [argValues, setArgValues] = useState<Record<string, string | boolean>>(initialArgValues);
|
||||
|
||||
@@ -126,7 +125,14 @@ function InitializedTemplateFunctionDialog({
|
||||
};
|
||||
|
||||
const debouncedTagText = useDebouncedValue(tagText.data ?? '', 400);
|
||||
const rendered = useRenderTemplate(debouncedTagText);
|
||||
const [renderKey, setRenderKey] = useState<string | null>(null);
|
||||
const rendered = useRenderTemplate(
|
||||
debouncedTagText,
|
||||
previewType !== 'none',
|
||||
previewType === 'click' ? 'send' : 'preview',
|
||||
previewType === 'live' ? renderKey + debouncedTagText : renderKey,
|
||||
);
|
||||
|
||||
const tooLarge = rendered.data ? rendered.data.length > 10000 : false;
|
||||
const dataContainsSecrets = useMemo(() => {
|
||||
for (const [name, value] of Object.entries(argValues)) {
|
||||
@@ -174,12 +180,12 @@ function InitializedTemplateFunctionDialog({
|
||||
)}
|
||||
</div>
|
||||
<div className="px-6 border-t border-t-border py-3 bg-surface-highlight w-full flex flex-col gap-4">
|
||||
{enablePreview ? (
|
||||
{previewType !== 'none' ? (
|
||||
<VStack className="w-full">
|
||||
<HStack space={0.5}>
|
||||
<HStack className="text-sm text-text-subtle" space={1.5}>
|
||||
Rendered Preview
|
||||
{rendered.isPending && <LoadingIcon size="xs" />}
|
||||
{rendered.isLoading && <LoadingIcon size="xs" />}
|
||||
</HStack>
|
||||
<IconButton
|
||||
size="xs"
|
||||
@@ -195,6 +201,7 @@ function InitializedTemplateFunctionDialog({
|
||||
</HStack>
|
||||
<InlineCode
|
||||
className={classNames(
|
||||
'relative',
|
||||
'whitespace-pre-wrap !select-text cursor-text max-h-[10rem] overflow-y-auto hide-scrollbars !border-text-subtlest',
|
||||
tooLarge && 'italic text-danger',
|
||||
)}
|
||||
@@ -212,6 +219,18 @@ function InitializedTemplateFunctionDialog({
|
||||
) : (
|
||||
rendered.data || <> </>
|
||||
)}
|
||||
<div className="absolute right-0 top-0 bottom-0 flex items-center">
|
||||
<IconButton
|
||||
size="xs"
|
||||
icon="refresh"
|
||||
className="text-text-subtle"
|
||||
title="Refresh preview"
|
||||
spin={rendered.isLoading}
|
||||
onClick={() => {
|
||||
setRenderKey(new Date().toISOString());
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</InlineCode>
|
||||
</VStack>
|
||||
) : (
|
||||
@@ -250,8 +269,16 @@ function collectArgumentValues(initialTokens: Tokens, templateFunction: Template
|
||||
if (!('name' in arg)) return;
|
||||
|
||||
const initialArg = initialArgs.find((a) => a.name === arg.name);
|
||||
const initialArgValue = initialArg?.value.type === 'str' ? initialArg?.value.text : undefined;
|
||||
initial[arg.name] = initialArgValue ?? arg.defaultValue ?? DYNAMIC_FORM_NULL_ARG;
|
||||
const initialArgValue =
|
||||
initialArg?.value.type === 'str'
|
||||
? initialArg?.value.text
|
||||
: initialArg?.value.type === 'bool'
|
||||
? initialArg.value.value
|
||||
: undefined;
|
||||
const value = initialArgValue ?? arg.defaultValue;
|
||||
if (value != null) {
|
||||
initial[arg.name] = value;
|
||||
}
|
||||
};
|
||||
|
||||
templateFunction.args.forEach(processArg);
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import type { Tokens } from '@yaakapp-internal/templates';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useActiveEnvironmentVariables } from '../hooks/useActiveEnvironmentVariables';
|
||||
import { useRenderTemplate } from '../hooks/useRenderTemplate';
|
||||
import { useTemplateTokensToString } from '../hooks/useTemplateTokensToString';
|
||||
import { Button } from './core/Button';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import { Select } from './core/Select';
|
||||
import { VStack } from './core/Stacks';
|
||||
|
||||
interface Props {
|
||||
initialTokens: Tokens;
|
||||
hide: () => void;
|
||||
onChange: (rawTag: string) => void;
|
||||
}
|
||||
|
||||
export function TemplateVariableDialog({ hide, onChange, initialTokens }: Props) {
|
||||
const variables = useActiveEnvironmentVariables();
|
||||
const [selectedVariableName, setSelectedVariableName] = useState<string>(() => {
|
||||
return initialTokens.tokens[0]?.type === 'tag' && initialTokens.tokens[0]?.val.type === 'var'
|
||||
? initialTokens.tokens[0]?.val.name
|
||||
: ''; // Should never happen
|
||||
});
|
||||
|
||||
const tokens: Tokens = useMemo(() => {
|
||||
const selectedVariable = variables.find((v) => v.name === selectedVariableName);
|
||||
return {
|
||||
tokens: [
|
||||
{
|
||||
type: 'tag',
|
||||
val: {
|
||||
type: 'var',
|
||||
name: selectedVariable?.name ?? '',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [selectedVariableName, variables]);
|
||||
|
||||
const tagText = useTemplateTokensToString(tokens);
|
||||
|
||||
const handleDone = useCallback(async () => {
|
||||
if (tagText.data != null) {
|
||||
onChange(tagText.data);
|
||||
}
|
||||
hide();
|
||||
}, [hide, onChange, tagText.data]);
|
||||
|
||||
const rendered = useRenderTemplate(tagText.data ?? '');
|
||||
|
||||
return (
|
||||
<VStack className="pb-3" space={4}>
|
||||
<VStack space={2}>
|
||||
<Select
|
||||
name="variable"
|
||||
label="Select Variable"
|
||||
value={selectedVariableName}
|
||||
options={variables.map((v) => ({ label: v.name, value: v.name }))}
|
||||
onChange={setSelectedVariableName}
|
||||
/>
|
||||
</VStack>
|
||||
<VStack>
|
||||
<div className="text-sm text-text-subtle">Preview</div>
|
||||
<InlineCode className="select-text cursor-text">{rendered.data}</InlineCode>
|
||||
</VStack>
|
||||
<Button color="primary" onClick={handleDone}>
|
||||
Done
|
||||
</Button>
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
@@ -9,7 +9,7 @@ export interface BannerProps {
|
||||
|
||||
export function Banner({ children, className, color }: BannerProps) {
|
||||
return (
|
||||
<div className="w-full mb-auto grid grid-rows-1 max-h-full">
|
||||
<div className="w-auto grid grid-rows-1 max-h-full flex-0">
|
||||
<div
|
||||
className={classNames(
|
||||
className,
|
||||
@@ -18,6 +18,7 @@ export function Banner({ children, className, color }: BannerProps) {
|
||||
'border border-border border-dashed',
|
||||
'px-4 py-2 rounded-lg select-auto',
|
||||
'overflow-auto text-text',
|
||||
'mb-auto', // Don't stretch all the way down if the parent is in grid or flexbox
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -723,6 +723,7 @@ function FileActionsDropdown({
|
||||
id: 'content-type',
|
||||
title: 'Override Content-Type',
|
||||
label: 'Content-Type',
|
||||
required: false,
|
||||
placeholder: 'text/plain',
|
||||
defaultValue: pair.contentType ?? '',
|
||||
confirmText: 'Set',
|
||||
|
||||
@@ -16,6 +16,7 @@ export function Prompt({
|
||||
label,
|
||||
defaultValue,
|
||||
placeholder,
|
||||
password,
|
||||
onResult,
|
||||
required,
|
||||
confirmText,
|
||||
@@ -36,10 +37,10 @@ export function Prompt({
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<PlainInput
|
||||
hideLabel
|
||||
autoSelect
|
||||
required={required}
|
||||
placeholder={placeholder ?? 'Enter text'}
|
||||
type={password ? 'password' : 'text'}
|
||||
label={label}
|
||||
defaultValue={defaultValue}
|
||||
onChange={setValue}
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { RenderPurpose } from '@yaakapp-internal/plugins';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { minPromiseMillis } from '../lib/minPromiseMillis';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useActiveEnvironment } from './useActiveEnvironment';
|
||||
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||
|
||||
export function useRenderTemplate(template: string) {
|
||||
export function useRenderTemplate(
|
||||
template: string,
|
||||
enabled: boolean,
|
||||
purpose: RenderPurpose,
|
||||
refreshKey: string | null,
|
||||
) {
|
||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom) ?? 'n/a';
|
||||
const environmentId = useActiveEnvironment()?.id ?? null;
|
||||
return useQuery<string>({
|
||||
refetchOnWindowFocus: false,
|
||||
queryKey: ['render_template', template, workspaceId, environmentId],
|
||||
queryFn: () => minPromiseMillis(renderTemplate({ template, workspaceId, environmentId }), 200),
|
||||
enabled,
|
||||
queryKey: ['render_template', workspaceId, environmentId, refreshKey, purpose],
|
||||
queryFn: () =>
|
||||
minPromiseMillis(renderTemplate({ template, workspaceId, environmentId, purpose }), 300),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,12 +27,14 @@ export async function renderTemplate({
|
||||
template,
|
||||
workspaceId,
|
||||
environmentId,
|
||||
purpose,
|
||||
}: {
|
||||
template: string;
|
||||
workspaceId: string;
|
||||
environmentId: string | null;
|
||||
purpose: RenderPurpose;
|
||||
}): Promise<string> {
|
||||
return invokeCmd('cmd_render_template', { template, workspaceId, environmentId });
|
||||
return invokeCmd('cmd_render_template', { template, workspaceId, environmentId, purpose });
|
||||
}
|
||||
|
||||
export async function decryptTemplate({
|
||||
|
||||
@@ -6,7 +6,13 @@ import { showDialog } from './dialog';
|
||||
type PromptArgs = Pick<DialogProps, 'title' | 'description'> &
|
||||
Omit<PromptProps, 'onClose' | 'onCancel' | 'onResult'> & { id: string };
|
||||
|
||||
export async function showPrompt({ id, title, description, ...props }: PromptArgs) {
|
||||
export async function showPrompt({
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
required = true,
|
||||
...props
|
||||
}: PromptArgs) {
|
||||
return new Promise((resolve: PromptProps['onResult']) => {
|
||||
showDialog({
|
||||
id,
|
||||
@@ -21,6 +27,7 @@ export async function showPrompt({ id, title, description, ...props }: PromptArg
|
||||
},
|
||||
render: ({ hide }) =>
|
||||
Prompt({
|
||||
required,
|
||||
onCancel: () => {
|
||||
// Click cancel button within dialog
|
||||
resolve(null);
|
||||
|
||||
@@ -11,6 +11,7 @@ export async function renameModelWithPrompt(model: Extract<AnyModel, { name: str
|
||||
const name = await showPrompt({
|
||||
id: 'rename-request',
|
||||
title: 'Rename Request',
|
||||
required: false,
|
||||
description:
|
||||
model.name === '' ? (
|
||||
'Enter a new name'
|
||||
|
||||
@@ -20,7 +20,13 @@ export function setWorkspaceSearchParams(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
search: (prev: any) => {
|
||||
// console.log('Navigating to', { prev, search });
|
||||
return { ...prev, ...search };
|
||||
const o = { ...prev, ...search };
|
||||
for (const k of Object.keys(o)) {
|
||||
if (o[k] == null) {
|
||||
delete o[k];
|
||||
}
|
||||
}
|
||||
return o;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user