mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-19 09:07:49 +01:00
Compare commits
9 Commits
v2024.8.0-
...
v2024.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
badcbc7aef | ||
|
|
4b91601b98 | ||
|
|
93e0202b86 | ||
|
|
e75d6abe33 | ||
|
|
24a4e3494e | ||
|
|
124fb35dcd | ||
|
|
1aa2839c51 | ||
|
|
8d3260f394 | ||
|
|
7e194b9148 |
8
package-lock.json
generated
8
package-lock.json
generated
@@ -27,7 +27,7 @@
|
||||
"@tauri-apps/plugin-log": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-rc.0",
|
||||
"@yaakapp/api": "^0.1.11",
|
||||
"@yaakapp/api": "^0.1.13",
|
||||
"buffer": "^6.0.3",
|
||||
"classnames": "^2.3.2",
|
||||
"cm6-graphql": "^0.0.9",
|
||||
@@ -2990,9 +2990,9 @@
|
||||
"integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ=="
|
||||
},
|
||||
"node_modules/@yaakapp/api": {
|
||||
"version": "0.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/api/-/api-0.1.11.tgz",
|
||||
"integrity": "sha512-dRZAXrQpftWygy9nJXiIYPzLA9om6reO/RiEacMe9RKqMjNG6FRF8cGmj7BcdyreizJOkH/DmcOpxn09kDD0XA==",
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/api/-/api-0.1.13.tgz",
|
||||
"integrity": "sha512-FSYPHZV0mP967w63VXi9zYP81hPo3vjSW3/UElJLuF/8ig6WmG4p1q2oYos4Ik267Z3qSQAGN5dPMfuk3DAnBA==",
|
||||
"dependencies": {
|
||||
"@types/node": "^22.0.0"
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"@tauri-apps/plugin-log": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-rc.0",
|
||||
"@yaakapp/api": "^0.1.11",
|
||||
"@yaakapp/api": "^0.1.13",
|
||||
"buffer": "^6.0.3",
|
||||
"classnames": "^2.3.2",
|
||||
"cm6-graphql": "^0.0.9",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@yaakapp/api",
|
||||
"version": "0.1.11",
|
||||
"version": "0.1.13",
|
||||
"main": "lib/index.js",
|
||||
"typings": "./lib/index.d.ts",
|
||||
"files": [
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { CallTemplateFunctionPurpose } from "./CallTemplateFunctionPurpose";
|
||||
import type { RenderPurpose } from "./RenderPurpose";
|
||||
|
||||
export type CallTemplateFunctionArgs = { purpose: CallTemplateFunctionPurpose, values: { [key: string]: string }, };
|
||||
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key: string]: string }, };
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { HttpRequest } from "./HttpRequest";
|
||||
import type { RenderPurpose } from "./RenderPurpose";
|
||||
|
||||
export type RenderHttpRequestRequest = { httpRequest: HttpRequest, };
|
||||
export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, };
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type CallTemplateFunctionPurpose = "send" | "preview";
|
||||
export type RenderPurpose = "send" | "preview";
|
||||
@@ -1,3 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Settings = { id: string, model: "settings", createdAt: string, updatedAt: string, theme: string, appearance: string, themeDark: string, themeLight: string, updateChannel: string, interfaceFontSize: number, interfaceScale: number, editorFontSize: number, editorSoftWrap: boolean, openWorkspaceNewWindow: boolean | null, };
|
||||
export type Settings = { id: string, model: "settings", createdAt: string, updatedAt: string, theme: string, appearance: string, themeDark: string, themeLight: string, updateChannel: string, interfaceFontSize: number, interfaceScale: number, editorFontSize: number, editorSoftWrap: boolean, telemetry: boolean, openWorkspaceNewWindow: boolean | null, };
|
||||
|
||||
@@ -5,7 +5,6 @@ export type * from './themes';
|
||||
export * from './gen/BootRequest';
|
||||
export * from './gen/BootResponse';
|
||||
export * from './gen/CallHttpRequestActionArgs';
|
||||
export * from './gen/CallTemplateFunctionPurpose';
|
||||
export * from './gen/CallHttpRequestActionRequest';
|
||||
export * from './gen/CallTemplateFunctionRequest';
|
||||
export * from './gen/CallTemplateFunctionResponse';
|
||||
@@ -48,6 +47,7 @@ export * from './gen/KeyValue';
|
||||
export * from './gen/Model';
|
||||
export * from './gen/RenderHttpRequestRequest';
|
||||
export * from './gen/RenderHttpRequestResponse';
|
||||
export * from './gen/RenderPurpose';
|
||||
export * from './gen/SendHttpRequestRequest';
|
||||
export * from './gen/SendHttpRequestResponse';
|
||||
export * from './gen/SendHttpRequestResponse';
|
||||
|
||||
@@ -64,6 +64,9 @@ new Promise<void>(async (resolve, reject) => {
|
||||
}
|
||||
|
||||
function sendEvent(event: InternalEvent) {
|
||||
if (event.payload.type !== 'empty_response') {
|
||||
console.log('Sending event to app', event.id, event.payload.type);
|
||||
}
|
||||
parentPort!.postMessage(event);
|
||||
}
|
||||
|
||||
@@ -77,8 +80,8 @@ new Promise<void>(async (resolve, reject) => {
|
||||
const promise = new Promise<InternalEventPayload>(async (resolve) => {
|
||||
const cb = (event: InternalEvent) => {
|
||||
if (event.replyId === eventToSend.id) {
|
||||
resolve(event.payload); // Not type-safe but oh well
|
||||
parentPort!.off('message', cb); // Unlisten, now that we're done
|
||||
resolve(event.payload); // Not type-safe but oh well
|
||||
}
|
||||
};
|
||||
parentPort!.on('message', cb);
|
||||
@@ -110,18 +113,18 @@ new Promise<void>(async (resolve, reject) => {
|
||||
},
|
||||
},
|
||||
httpRequest: {
|
||||
async getById({ id }) {
|
||||
const payload = { type: 'get_http_request_by_id_request', id } as const;
|
||||
async getById(args) {
|
||||
const payload = { type: 'get_http_request_by_id_request', ...args } as const;
|
||||
const { httpRequest } = await sendAndWaitForReply<GetHttpRequestByIdResponse>(payload);
|
||||
return httpRequest;
|
||||
},
|
||||
async send({ httpRequest }) {
|
||||
const payload = { type: 'send_http_request_request', httpRequest } as const;
|
||||
async send(args) {
|
||||
const payload = { type: 'send_http_request_request', ...args } as const;
|
||||
const { httpResponse } = await sendAndWaitForReply<SendHttpRequestResponse>(payload);
|
||||
return httpResponse;
|
||||
},
|
||||
async render({ httpRequest }) {
|
||||
const payload = { type: 'render_http_request_request', httpRequest } as const;
|
||||
async render(args) {
|
||||
const payload = { type: 'render_http_request_request', ...args } as const;
|
||||
const result = await sendAndWaitForReply<RenderHttpRequestResponse>(payload);
|
||||
return result.httpRequest;
|
||||
},
|
||||
@@ -130,8 +133,6 @@ new Promise<void>(async (resolve, reject) => {
|
||||
|
||||
// Message comes into the plugin to be processed
|
||||
parentPort!.on('message', async ({ payload, id: replyId }: InternalEvent) => {
|
||||
console.log(`Received ${payload.type}`);
|
||||
|
||||
try {
|
||||
if (payload.type === 'boot_request') {
|
||||
const payload: InternalEventPayload = {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE settings ADD COLUMN telemetry BOOLEAN DEFAULT TRUE;
|
||||
@@ -5,9 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use tauri::{Manager, Runtime, WebviewWindow};
|
||||
|
||||
use yaak_models::queries::{
|
||||
generate_id, get_key_value_int, get_key_value_string, set_key_value_int, set_key_value_string,
|
||||
};
|
||||
use yaak_models::queries::{generate_id, get_key_value_int, get_key_value_string, get_or_create_settings, set_key_value_int, set_key_value_string};
|
||||
|
||||
use crate::is_dev;
|
||||
|
||||
@@ -157,6 +155,7 @@ pub async fn track_event<R: Runtime>(
|
||||
action: AnalyticsAction,
|
||||
attributes: Option<Value>,
|
||||
) {
|
||||
|
||||
let id = get_id(w).await;
|
||||
let event = format!("{}.{}", resource, action);
|
||||
let attributes_json = attributes.unwrap_or("{}".to_string().into()).to_string();
|
||||
@@ -186,9 +185,15 @@ pub async fn track_event<R: Runtime>(
|
||||
.get(format!("{base_url}/t/e"))
|
||||
.query(¶ms);
|
||||
|
||||
let settings = get_or_create_settings(w).await;
|
||||
if !settings.telemetry {
|
||||
info!("Track event (disabled): {}", event);
|
||||
return
|
||||
}
|
||||
|
||||
// Disable analytics actual sending in dev
|
||||
if is_dev() {
|
||||
debug!("track: {} {}", event, attributes_json);
|
||||
debug!("Track event: {} {}", event, attributes_json);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1942,24 +1942,21 @@ fn monitor_plugin_events<R: Runtime>(app_handle: &AppHandle<R>) {
|
||||
let plugin_manager: State<'_, PluginManager> = app_handle.state();
|
||||
let (_rx_id, mut rx) = plugin_manager.subscribe().await;
|
||||
|
||||
let app_handle = app_handle.clone();
|
||||
while let Some(event) = rx.recv().await {
|
||||
let payload = match handle_plugin_event(&app_handle, &event).await {
|
||||
Some(e) => e,
|
||||
None => continue,
|
||||
};
|
||||
if let Err(e) = plugin_manager.reply(&event, &payload).await {
|
||||
warn!("Failed to reply to plugin manager: {}", e)
|
||||
}
|
||||
let app_handle = app_handle.clone();
|
||||
|
||||
// We might have recursive back-and-forth calls between app and plugin, so we don't
|
||||
// want to block here
|
||||
tauri::async_runtime::spawn(async move {
|
||||
handle_plugin_event(&app_handle, &event).await;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn handle_plugin_event<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
event: &InternalEvent,
|
||||
) -> Option<InternalEventPayload> {
|
||||
let event = match event.clone().payload {
|
||||
async fn handle_plugin_event<R: Runtime>(app_handle: &AppHandle<R>, event: &InternalEvent) {
|
||||
info!("Got event to app {}", event.id);
|
||||
let response_event: Option<InternalEventPayload> = match event.clone().payload {
|
||||
InternalEventPayload::CopyTextRequest(req) => {
|
||||
app_handle
|
||||
.clipboard()
|
||||
@@ -1992,7 +1989,7 @@ async fn handle_plugin_event<R: Runtime>(
|
||||
))
|
||||
}
|
||||
InternalEventPayload::RenderHttpRequestRequest(req) => {
|
||||
let w = get_focused_window_no_lock(app_handle)?;
|
||||
let w = get_focused_window_no_lock(app_handle).expect("No focused window");
|
||||
let workspace = get_workspace(app_handle, req.http_request.workspace_id.as_str())
|
||||
.await
|
||||
.expect("Failed to get workspace for request");
|
||||
@@ -2007,13 +2004,8 @@ async fn handle_plugin_event<R: Runtime>(
|
||||
Some(id) => get_environment(&w, id.as_str()).await.ok(),
|
||||
};
|
||||
let cb = &*app_handle.state::<PluginTemplateCallback>();
|
||||
let rendered_http_request = render_http_request(
|
||||
&req.http_request,
|
||||
&workspace,
|
||||
environment.as_ref(),
|
||||
cb,
|
||||
)
|
||||
.await;
|
||||
let rendered_http_request =
|
||||
render_http_request(&req.http_request, &workspace, environment.as_ref(), cb).await;
|
||||
Some(InternalEventPayload::RenderHttpRequestResponse(
|
||||
RenderHttpRequestResponse {
|
||||
http_request: rendered_http_request,
|
||||
@@ -2021,7 +2013,7 @@ async fn handle_plugin_event<R: Runtime>(
|
||||
))
|
||||
}
|
||||
InternalEventPayload::SendHttpRequestRequest(req) => {
|
||||
let w = get_focused_window_no_lock(app_handle)?;
|
||||
let w = get_focused_window_no_lock(app_handle).expect("No focused window");
|
||||
let url = w.url().unwrap();
|
||||
let mut query_pairs = url.query_pairs();
|
||||
|
||||
@@ -2057,7 +2049,7 @@ async fn handle_plugin_event<R: Runtime>(
|
||||
|
||||
let http_response = match result {
|
||||
Ok(r) => r,
|
||||
Err(_e) => return None,
|
||||
Err(_e) => return,
|
||||
};
|
||||
|
||||
Some(InternalEventPayload::SendHttpRequestResponse(
|
||||
@@ -2067,7 +2059,12 @@ async fn handle_plugin_event<R: Runtime>(
|
||||
_ => None,
|
||||
};
|
||||
|
||||
event
|
||||
if let Some(e) = response_event {
|
||||
let plugin_manager: State<'_, PluginManager> = app_handle.state();
|
||||
if let Err(e) = plugin_manager.reply(&event, &e).await {
|
||||
warn!("Failed to reply to plugin manager: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// app_handle.get_focused_window locks, so this one is a non-locking version, safe for use in async context
|
||||
|
||||
@@ -1,32 +1,67 @@
|
||||
use std::collections::HashMap;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use yaak_plugin_runtime::events::CallTemplateFunctionPurpose;
|
||||
use yaak_plugin_runtime::events::{RenderPurpose, TemplateFunctionArg};
|
||||
use yaak_plugin_runtime::manager::PluginManager;
|
||||
use yaak_templates::TemplateCallback;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PluginTemplateCallback {
|
||||
app_handle: AppHandle,
|
||||
purpose: CallTemplateFunctionPurpose,
|
||||
purpose: RenderPurpose,
|
||||
}
|
||||
|
||||
impl PluginTemplateCallback {
|
||||
pub fn new(app_handle: AppHandle) -> PluginTemplateCallback {
|
||||
PluginTemplateCallback { app_handle, purpose: CallTemplateFunctionPurpose::Preview }
|
||||
PluginTemplateCallback {
|
||||
app_handle,
|
||||
purpose: RenderPurpose::Preview,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn for_send(&self) -> PluginTemplateCallback {
|
||||
let mut v = self.clone();
|
||||
v.purpose = CallTemplateFunctionPurpose::Send;
|
||||
v.purpose = RenderPurpose::Send;
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
impl TemplateCallback for PluginTemplateCallback {
|
||||
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String, String> {
|
||||
// The beta named the function `Response` but was changed in stable.
|
||||
// Keep this here for a while because there's no easy way to migrate
|
||||
let fn_name = if fn_name == "Response" {
|
||||
"response"
|
||||
} else {
|
||||
fn_name
|
||||
};
|
||||
|
||||
let plugin_manager = self.app_handle.state::<PluginManager>();
|
||||
let function = plugin_manager
|
||||
.get_template_functions()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.iter()
|
||||
.flat_map(|f| f.functions.clone())
|
||||
.find(|f| f.name == fn_name)
|
||||
.ok_or("")?;
|
||||
|
||||
let mut args_with_defaults = args.clone();
|
||||
|
||||
// Fill in default values for all args
|
||||
for a_def in function.args {
|
||||
let base = match a_def {
|
||||
TemplateFunctionArg::Text(a) => a.base,
|
||||
TemplateFunctionArg::Select(a) => a.base,
|
||||
TemplateFunctionArg::Checkbox(a) => a.base,
|
||||
TemplateFunctionArg::HttpRequest(a) => a.base,
|
||||
};
|
||||
if let None = args_with_defaults.get(base.name.as_str()) {
|
||||
args_with_defaults.insert(base.name, base.default_value.unwrap_or_default());
|
||||
}
|
||||
}
|
||||
|
||||
let resp = plugin_manager
|
||||
.call_template_function(fn_name, args, self.purpose.clone())
|
||||
.call_template_function(fn_name, args_with_defaults, self.purpose.clone())
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(resp.unwrap_or_default())
|
||||
|
||||
@@ -23,6 +23,7 @@ pub struct Settings {
|
||||
pub interface_scale: i32,
|
||||
pub editor_font_size: i32,
|
||||
pub editor_soft_wrap: bool,
|
||||
pub telemetry: bool,
|
||||
pub open_workspace_new_window: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -43,6 +44,7 @@ pub enum SettingsIden {
|
||||
InterfaceScale,
|
||||
EditorFontSize,
|
||||
EditorSoftWrap,
|
||||
Telemetry,
|
||||
OpenWorkspaceNewWindow,
|
||||
}
|
||||
|
||||
@@ -64,6 +66,7 @@ impl<'s> TryFrom<&Row<'s>> for Settings {
|
||||
interface_scale: r.get("interface_scale")?,
|
||||
editor_font_size: r.get("editor_font_size")?,
|
||||
editor_soft_wrap: r.get("editor_soft_wrap")?,
|
||||
telemetry: r.get("telemetry")?,
|
||||
open_workspace_new_window: r.get("open_workspace_new_window")?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -799,6 +799,10 @@ pub async fn update_settings<R: Runtime>(
|
||||
SettingsIden::EditorSoftWrap,
|
||||
settings.editor_soft_wrap.into(),
|
||||
),
|
||||
(
|
||||
SettingsIden::Telemetry,
|
||||
settings.telemetry.into(),
|
||||
),
|
||||
(
|
||||
SettingsIden::OpenWorkspaceNewWindow,
|
||||
settings.open_workspace_new_window.into(),
|
||||
|
||||
@@ -17,18 +17,10 @@ pub enum Error {
|
||||
GrpcSendErr(#[from] SendError<tonic::Result<EventStreamEvent>>),
|
||||
#[error("JSON error")]
|
||||
JsonErr(#[from] serde_json::Error),
|
||||
#[error("Plugin not found error")]
|
||||
#[error("Plugin not found: {0}")]
|
||||
PluginNotFoundErr(String),
|
||||
#[error("unknown error")]
|
||||
MissingCallbackIdErr(String),
|
||||
#[error("Missing callback ID error")]
|
||||
MissingCallbackErr(String),
|
||||
#[error("No plugins found")]
|
||||
NoPluginsErr(String),
|
||||
#[error("Plugin error")]
|
||||
#[error("Plugin error: {0}")]
|
||||
PluginErr(String),
|
||||
#[error("Unknown error")]
|
||||
UnknownErr(String),
|
||||
}
|
||||
|
||||
impl Into<String> for Error {
|
||||
|
||||
@@ -153,6 +153,7 @@ pub struct CopyTextRequest {
|
||||
#[ts(export)]
|
||||
pub struct RenderHttpRequestRequest {
|
||||
pub http_request: HttpRequest,
|
||||
pub purpose: RenderPurpose,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
@@ -289,21 +290,21 @@ pub struct CallTemplateFunctionResponse {
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct CallTemplateFunctionArgs {
|
||||
pub purpose: CallTemplateFunctionPurpose,
|
||||
pub purpose: RenderPurpose,
|
||||
pub values: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export)]
|
||||
pub enum CallTemplateFunctionPurpose {
|
||||
pub enum RenderPurpose {
|
||||
Send,
|
||||
Preview,
|
||||
}
|
||||
|
||||
impl Default for CallTemplateFunctionPurpose {
|
||||
impl Default for RenderPurpose {
|
||||
fn default() -> Self {
|
||||
CallTemplateFunctionPurpose::Preview
|
||||
RenderPurpose::Preview
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::error::Result;
|
||||
use crate::events::{
|
||||
CallHttpRequestActionRequest, CallTemplateFunctionArgs, CallTemplateFunctionPurpose,
|
||||
CallHttpRequestActionRequest, CallTemplateFunctionArgs, RenderPurpose,
|
||||
CallTemplateFunctionRequest, CallTemplateFunctionResponse, FilterRequest, FilterResponse,
|
||||
GetHttpRequestActionsRequest, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse,
|
||||
ImportRequest, ImportResponse, InternalEvent, InternalEventPayload,
|
||||
@@ -115,7 +115,7 @@ impl PluginManager {
|
||||
&self,
|
||||
fn_name: &str,
|
||||
args: HashMap<String, String>,
|
||||
purpose: CallTemplateFunctionPurpose,
|
||||
purpose: RenderPurpose,
|
||||
) -> Result<Option<String>> {
|
||||
let req = CallTemplateFunctionRequest {
|
||||
name: fn_name.to_string(),
|
||||
@@ -155,7 +155,7 @@ impl PluginManager {
|
||||
});
|
||||
|
||||
match result {
|
||||
None => Err(PluginErr("No import responses found".to_string())),
|
||||
None => Err(PluginErr("No importers found for file contents".to_string())),
|
||||
Some((resp, ref_id)) => {
|
||||
let plugin = self.server.plugin_by_ref_id(ref_id.as_str()).await?;
|
||||
let plugin_name = plugin.name().await;
|
||||
|
||||
@@ -9,7 +9,7 @@ use tonic::codegen::tokio_stream::wrappers::ReceiverStream;
|
||||
use tonic::codegen::tokio_stream::{Stream, StreamExt};
|
||||
use tonic::{Request, Response, Status, Streaming};
|
||||
|
||||
use crate::error::Error::{NoPluginsErr, PluginNotFoundErr};
|
||||
use crate::error::Error::PluginNotFoundErr;
|
||||
use crate::error::Result;
|
||||
use crate::events::{BootRequest, BootResponse, InternalEvent, InternalEventPayload};
|
||||
use crate::server::plugin_runtime::plugin_runtime_server::PluginRuntime;
|
||||
@@ -53,7 +53,11 @@ impl PluginHandle {
|
||||
}
|
||||
|
||||
pub async fn send(&self, event: &InternalEvent) -> Result<()> {
|
||||
info!("Sending event {} {:?}", event.id, self.name().await);
|
||||
info!(
|
||||
"Sending event to plugin {} {:?}",
|
||||
event.id,
|
||||
self.name().await
|
||||
);
|
||||
self.to_plugin_tx
|
||||
.lock()
|
||||
.await
|
||||
@@ -90,9 +94,9 @@ impl PluginRuntimeGrpcServer {
|
||||
|
||||
pub async fn subscribe(&self) -> (String, Receiver<InternalEvent>) {
|
||||
let (tx, rx) = mpsc::channel(128);
|
||||
let id = generate_id();
|
||||
self.subscribers.lock().await.insert(id.clone(), tx);
|
||||
(id, rx)
|
||||
let rx_id = generate_id();
|
||||
self.subscribers.lock().await.insert(rx_id.clone(), tx);
|
||||
(rx_id, rx)
|
||||
}
|
||||
|
||||
pub async fn unsubscribe(&self, rx_id: &str) {
|
||||
@@ -187,14 +191,11 @@ impl PluginRuntimeGrpcServer {
|
||||
pub async fn plugin_by_ref_id(&self, ref_id: &str) -> Result<PluginHandle> {
|
||||
let plugins = self.plugin_ref_to_plugin.lock().await;
|
||||
if plugins.is_empty() {
|
||||
return Err(NoPluginsErr("Send failed because no plugins exist".into()));
|
||||
return Err(PluginNotFoundErr(ref_id.into()));
|
||||
}
|
||||
|
||||
match plugins.get(ref_id) {
|
||||
None => {
|
||||
let msg = format!("Failed to find plugin for id {ref_id}");
|
||||
Err(PluginNotFoundErr(msg))
|
||||
}
|
||||
None => Err(PluginNotFoundErr(ref_id.into())),
|
||||
Some(p) => Ok(p.to_owned()),
|
||||
}
|
||||
}
|
||||
@@ -202,7 +203,7 @@ impl PluginRuntimeGrpcServer {
|
||||
pub async fn plugin_by_name(&self, plugin_name: &str) -> Result<PluginHandle> {
|
||||
let plugins = self.plugin_ref_to_plugin.lock().await;
|
||||
if plugins.is_empty() {
|
||||
return Err(NoPluginsErr("Send failed because no plugins exist".into()));
|
||||
return Err(PluginNotFoundErr(plugin_name.into()));
|
||||
}
|
||||
|
||||
for p in plugins.values() {
|
||||
@@ -211,17 +212,20 @@ impl PluginRuntimeGrpcServer {
|
||||
}
|
||||
}
|
||||
|
||||
let msg = format!("Failed to find plugin for {plugin_name}");
|
||||
Err(PluginNotFoundErr(msg))
|
||||
Err(PluginNotFoundErr(plugin_name.into()))
|
||||
}
|
||||
|
||||
pub async fn send(&self, payload: &InternalEventPayload, plugin_ref_id: &str, reply_id: Option<String>)-> Result<()> {
|
||||
pub async fn send(
|
||||
&self,
|
||||
payload: &InternalEventPayload,
|
||||
plugin_ref_id: &str,
|
||||
reply_id: Option<String>,
|
||||
) -> Result<()> {
|
||||
let plugin = self.plugin_by_ref_id(plugin_ref_id).await?;
|
||||
let event = plugin.build_event_to_send(payload, reply_id);
|
||||
plugin.send(&event).await
|
||||
}
|
||||
|
||||
|
||||
pub async fn send_to_plugin(
|
||||
&self,
|
||||
plugin_name: &str,
|
||||
@@ -229,7 +233,7 @@ impl PluginRuntimeGrpcServer {
|
||||
) -> Result<InternalEvent> {
|
||||
let plugins = self.plugin_ref_to_plugin.lock().await;
|
||||
if plugins.is_empty() {
|
||||
return Err(NoPluginsErr("Send failed because no plugins exist".into()));
|
||||
return Err(PluginNotFoundErr(plugin_name.into()));
|
||||
}
|
||||
|
||||
let mut plugin = None;
|
||||
@@ -246,10 +250,7 @@ impl PluginRuntimeGrpcServer {
|
||||
plugin.send(&event).await?;
|
||||
Ok(event)
|
||||
}
|
||||
None => {
|
||||
let msg = format!("Failed to find plugin for {plugin_name}");
|
||||
Err(PluginNotFoundErr(msg))
|
||||
}
|
||||
None => Err(PluginNotFoundErr(plugin_name.into())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,7 +398,7 @@ impl PluginRuntime for PluginRuntimeGrpcServer {
|
||||
for tx in subscribers.values() {
|
||||
// Emit event to the channel for server to handle
|
||||
if let Err(e) = tx.try_send(event.clone()) {
|
||||
println!("Failed to send to server channel. Receiver probably isn't listening: {:?}", e);
|
||||
println!("Failed to send to server channel (n={}). Receiver probably isn't listening: {:?}", subscribers.len(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Cookie } from '@yaakapp/api';
|
||||
import { useCookieJars } from '../hooks/useCookieJars';
|
||||
import { useUpdateCookieJar } from '../hooks/useUpdateCookieJar';
|
||||
import type { Cookie } from '../lib/models/Cookie';
|
||||
import { cookieDomain } from '../lib/models';
|
||||
import { Banner } from './core/Banner';
|
||||
import { IconButton } from './core/IconButton';
|
||||
|
||||
@@ -77,6 +77,12 @@ export function SettingsGeneral() {
|
||||
{ label: 'New Window', value: 'new' },
|
||||
]}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={settings.telemetry}
|
||||
title="Send Usage Statistics"
|
||||
onChange={(telemetry) => updateSettings.mutate({ telemetry })}
|
||||
/>
|
||||
|
||||
<Separator className="my-4" />
|
||||
|
||||
<Heading size={2}>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
import { forceParsing } from '@codemirror/language';
|
||||
import { Compartment, EditorState, type Extension } from '@codemirror/state';
|
||||
import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
|
||||
import type { EnvironmentVariable, TemplateFunction } from '@yaakapp/api';
|
||||
@@ -297,6 +298,12 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
});
|
||||
|
||||
view = new EditorView({ state, parent: container });
|
||||
|
||||
// For large documents, the parser may parse the max number of lines and fail to add
|
||||
// things like fold markers because of it.
|
||||
// This forces it to parse more but keeps the timeout to the default of 100ms.
|
||||
forceParsing(view, 9e6, 100);
|
||||
|
||||
cm.current = { view, languageCompartment };
|
||||
if (autoFocus) {
|
||||
view.focus();
|
||||
|
||||
@@ -68,7 +68,7 @@ const syntaxExtensions: Record<string, LanguageSupport> = {
|
||||
'application/graphql': graphqlLanguageSupport(),
|
||||
'application/json': json(),
|
||||
'application/javascript': javascript(),
|
||||
'text/html': xml(), // HTML as xml because HTML is oddly slow
|
||||
'text/html': xml(), // HTML as XML because HTML is oddly slow
|
||||
'application/xml': xml(),
|
||||
'text/xml': xml(),
|
||||
url: url(),
|
||||
|
||||
@@ -7,7 +7,7 @@ import { genericCompletion } from '../genericCompletion';
|
||||
import { textLanguageName } from '../text/extension';
|
||||
import type { TwigCompletionOption } from './completion';
|
||||
import { twigCompletion } from './completion';
|
||||
import { templateTags } from './templateTags';
|
||||
import { templateTagsPlugin } from './templateTags';
|
||||
import { parser as twigParser } from './twig';
|
||||
|
||||
export function twig({
|
||||
@@ -62,7 +62,7 @@ export function twig({
|
||||
return [
|
||||
language,
|
||||
base.support,
|
||||
templateTags(options, onClickMissingVariable),
|
||||
templateTagsPlugin(options, onClickMissingVariable),
|
||||
language.data.of({ autocomplete: completions }),
|
||||
base.language.data.of({ autocomplete: completions }),
|
||||
language.data.of({ autocomplete: genericCompletion(autocomplete) }),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { syntaxTree } from '@codemirror/language';
|
||||
import type { Range } from '@codemirror/state';
|
||||
import type { DecorationSet, ViewUpdate } from '@codemirror/view';
|
||||
import { Decoration, EditorView, ViewPlugin, WidgetType } from '@codemirror/view';
|
||||
import { BetterMatchDecorator } from '../BetterMatchDecorator';
|
||||
import { Decoration, ViewPlugin, WidgetType } from '@codemirror/view';
|
||||
import { EditorView } from 'codemirror';
|
||||
import type { TwigCompletionOption } from './completion';
|
||||
|
||||
class TemplateTagWidget extends WidgetType {
|
||||
@@ -22,7 +24,8 @@ class TemplateTagWidget extends WidgetType {
|
||||
this.option.name === other.option.name &&
|
||||
this.option.type === other.option.type &&
|
||||
this.option.value === other.option.value &&
|
||||
this.rawTag === other.rawTag
|
||||
this.rawTag === other.rawTag &&
|
||||
this.startPos === other.startPos
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,69 +58,91 @@ class TemplateTagWidget extends WidgetType {
|
||||
}
|
||||
}
|
||||
|
||||
export function templateTags(
|
||||
function templateTags(
|
||||
view: EditorView,
|
||||
options: TwigCompletionOption[],
|
||||
onClickMissingVariable: (name: string, rawTag: string, startPos: number) => void,
|
||||
) {
|
||||
const templateTagMatcher = new BetterMatchDecorator({
|
||||
regexp: /\$\{\[\s*(.+)(?!]})\s*]}/g,
|
||||
decoration(match, view, matchStartPos) {
|
||||
const matchEndPos = matchStartPos + match[0].length - 1;
|
||||
): DecorationSet {
|
||||
const widgets: Range<Decoration>[] = [];
|
||||
for (const { from, to } of view.visibleRanges) {
|
||||
syntaxTree(view.state).iterate({
|
||||
from,
|
||||
to,
|
||||
enter(node) {
|
||||
if (node.name == 'Tag') {
|
||||
// Don't decorate if the cursor is inside the match
|
||||
for (const r of view.state.selection.ranges) {
|
||||
if (r.from > node.from && r.to < node.to) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't decorate if the cursor is inside the match
|
||||
for (const r of view.state.selection.ranges) {
|
||||
if (r.from > matchStartPos && r.to <= matchEndPos) {
|
||||
return Decoration.replace({});
|
||||
const rawTag = view.state.doc.sliceString(node.from, node.to);
|
||||
|
||||
// TODO: Search `node.tree` instead of using Regex here
|
||||
const inner = rawTag.replace(/^\$\{\[\s*/, '').replace(/\s*]}$/, '');
|
||||
let name = inner.match(/(\w+)[(]/)?.[1] ?? inner;
|
||||
|
||||
// The beta named the function `Response` but was changed in stable.
|
||||
// Keep this here for a while because there's no easy way to migrate
|
||||
if (name === 'Response') {
|
||||
name = 'response';
|
||||
}
|
||||
|
||||
let option = options.find((v) => v.name === name);
|
||||
if (option == null) {
|
||||
option = {
|
||||
invalid: true,
|
||||
type: 'variable',
|
||||
name: inner,
|
||||
value: null,
|
||||
label: inner,
|
||||
onClick: () => onClickMissingVariable(name, rawTag, node.from),
|
||||
};
|
||||
}
|
||||
|
||||
const widget = new TemplateTagWidget(option, rawTag, node.from);
|
||||
const deco = Decoration.replace({ widget, inclusive: true });
|
||||
widgets.push(deco.range(node.from, node.to));
|
||||
}
|
||||
}
|
||||
|
||||
const innerTagMatch = match[1];
|
||||
if (innerTagMatch == null) {
|
||||
// Should never happen, but make TS happy
|
||||
console.warn('Group match was empty', match);
|
||||
return Decoration.replace({});
|
||||
}
|
||||
|
||||
// TODO: Replace this hacky match with a proper template parser
|
||||
const name = innerTagMatch.match(/\s*(\w+)[(\s]*/)?.[1] ?? innerTagMatch;
|
||||
|
||||
let option = options.find((v) => v.name === name);
|
||||
if (option == null) {
|
||||
option = {
|
||||
invalid: true,
|
||||
type: 'variable',
|
||||
name: innerTagMatch,
|
||||
value: null,
|
||||
label: innerTagMatch,
|
||||
onClick: () => onClickMissingVariable(name, match[0], matchStartPos),
|
||||
};
|
||||
}
|
||||
|
||||
return Decoration.replace({
|
||||
inclusive: true,
|
||||
widget: new TemplateTagWidget(option, match[0], matchStartPos),
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
return Decoration.set(widgets);
|
||||
}
|
||||
|
||||
export function templateTagsPlugin(
|
||||
options: TwigCompletionOption[],
|
||||
onClickMissingVariable: (name: string, tagValue: string, startPos: number) => void,
|
||||
) {
|
||||
return ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations: DecorationSet;
|
||||
|
||||
constructor(view: EditorView) {
|
||||
this.decorations = templateTagMatcher.createDeco(view);
|
||||
this.decorations = templateTags(view, options, onClickMissingVariable);
|
||||
}
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
this.decorations = templateTagMatcher.updateDeco(update, this.decorations);
|
||||
this.decorations = templateTags(update.view, options, onClickMissingVariable);
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: (instance) => instance.decorations,
|
||||
provide: (plugin) =>
|
||||
EditorView.atomicRanges.of((view) => {
|
||||
decorations(v) {
|
||||
return v.decorations;
|
||||
},
|
||||
provide(plugin) {
|
||||
return EditorView.atomicRanges.of((view) => {
|
||||
return view.plugin(plugin)?.decorations || Decoration.none;
|
||||
}),
|
||||
});
|
||||
},
|
||||
eventHandlers: {
|
||||
mousedown(e) {
|
||||
const target = e.target as HTMLElement;
|
||||
if (target.classList.contains('template-tag')) console.log('CLICKED TEMPLATE TAG');
|
||||
// return toggleBoolean(view, view.posAtDOM(target));
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { CookieJar } from '../lib/models';
|
||||
import type { CookieJar } from '@yaakapp/api';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useActiveWorkspace } from './useActiveWorkspace';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import type { CookieJar } from '@yaakapp/api';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import type { CookieJar } from '../lib/models';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useActiveWorkspace } from './useActiveWorkspace';
|
||||
import { usePrompt } from './usePrompt';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import type { CookieJar } from '@yaakapp/api';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import { trackEvent } from '../lib/analytics';
|
||||
import type { CookieJar } from '../lib/models';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useConfirm } from './useConfirm';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Settings } from '@yaakapp/api';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { atom } from 'jotai/index';
|
||||
import type { Settings } from '../lib/models/Settings';
|
||||
import { getSettings } from '../lib/store';
|
||||
|
||||
const settings = await getSettings();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import type { CookieJar } from '../lib/models';
|
||||
import type { CookieJar } from '@yaakapp/api';
|
||||
import { getCookieJar } from '../lib/store';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import type { Settings } from '../lib/models';
|
||||
import type { Settings } from '@yaakapp/api';
|
||||
import { getSettings } from '../lib/store';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import type { GrpcConnection, HttpResponse, HttpResponseHeader, Model } from '@yaakapp/api';
|
||||
import type { Cookie } from './models/Cookie';
|
||||
import type { CookieJar } from './models/CookieJar';
|
||||
import type { Settings } from './models/Settings';
|
||||
|
||||
export type { CookieJar, Cookie, Settings };
|
||||
import type { Cookie, GrpcConnection, HttpResponse, HttpResponseHeader, Model } from '@yaakapp/api';
|
||||
|
||||
export const BODY_TYPE_NONE = null;
|
||||
export const BODY_TYPE_GRAPHQL = 'graphql';
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { CookieDomain } from "./CookieDomain";
|
||||
import type { CookieExpires } from "./CookieExpires";
|
||||
|
||||
export type Cookie = { raw_cookie: string, domain: CookieDomain, expires: CookieExpires, path: [string, boolean], };
|
||||
@@ -1,3 +0,0 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type CookieDomain = { "HostOnly": string } | { "Suffix": string } | "NotPresent" | "Empty";
|
||||
@@ -1,3 +0,0 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type CookieExpires = { "AtUtc": string } | "SessionEnd";
|
||||
@@ -1,4 +0,0 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Cookie } from "./Cookie";
|
||||
|
||||
export type CookieJar = { id: string, model: "cookie_jar", createdAt: string, updatedAt: string, workspaceId: string, name: string, cookies: Array<Cookie>, };
|
||||
@@ -1,3 +0,0 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Settings = { id: string, model: "settings", createdAt: string, updatedAt: string, theme: string, appearance: string, themeDark: string, themeLight: string, updateChannel: string, interfaceFontSize: number, interfaceScale: number, editorFontSize: number, editorSoftWrap: boolean, openWorkspaceNewWindow: boolean | null, };
|
||||
@@ -1,6 +1,12 @@
|
||||
import type { Environment, Folder, GrpcRequest, HttpRequest, Workspace } from '@yaakapp/api';
|
||||
import type { CookieJar } from './models/CookieJar';
|
||||
import type { Settings } from './models/Settings';
|
||||
import type {
|
||||
CookieJar,
|
||||
Environment,
|
||||
Folder,
|
||||
GrpcRequest,
|
||||
HttpRequest,
|
||||
Settings,
|
||||
Workspace,
|
||||
} from '@yaakapp/api';
|
||||
import { invokeCmd } from './tauri';
|
||||
|
||||
export async function getSettings(): Promise<Settings> {
|
||||
|
||||
Reference in New Issue
Block a user