Compare commits

..

3 Commits

Author SHA1 Message Date
Gregory Schier
50ad4efad7 Protoc sidecar 2024-02-25 17:43:29 -08:00
Gregory Schier
79a3d9c8df Fix deletion in sidebar 2024-02-25 12:56:57 -08:00
Gregory Schier
b8e20d885f Fix create dropdown hotkey 2024-02-24 22:02:04 -08:00
14 changed files with 133 additions and 50 deletions

View File

@@ -7,6 +7,7 @@ permissions: write-all
jobs:
build-artifacts:
name: Build
strategy:
fail-fast: false
matrix:
@@ -37,7 +38,7 @@ jobs:
key: ${{ runner.os }}-cargo-${{ hashFiles('src-tauri/Cargo.lock') }}
- uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
cache: 'npm'
- name: install dependencies (ubuntu only)
if: matrix.os == 'ubuntu-20.04'

33
src-tauri/Cargo.lock generated
View File

@@ -1699,6 +1699,7 @@ dependencies = [
"protoc-bin-vendored",
"serde",
"serde_json",
"tauri",
"tokio",
"tokio-stream",
"tonic",
@@ -1763,9 +1764,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.3.21"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
dependencies = [
"bytes",
"fnv",
@@ -1773,7 +1774,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
"indexmap 1.9.3",
"indexmap 2.1.0",
"slab",
"tokio",
"tokio-util",
@@ -2974,6 +2975,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "os_pipe"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "overload"
version = "0.1.1"
@@ -4164,6 +4175,16 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "shared_child"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "signature"
version = "2.1.0"
@@ -4197,9 +4218,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.11.2"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "socket2"
@@ -4776,6 +4797,7 @@ dependencies = [
"once_cell",
"open",
"os_info",
"os_pipe",
"percent-encoding",
"rand 0.8.5",
"raw-window-handle",
@@ -4787,6 +4809,7 @@ dependencies = [
"serde_json",
"serde_repr",
"serialize-to-javascript",
"shared_child",
"state",
"sys-locale",
"tar",

View File

@@ -44,6 +44,7 @@ tauri = { version = "1.5.4", features = [
"os-all",
"protocol-asset",
"shell-open",
"shell-sidecar",
"updater",
"window-close",
"window-maximize",

View File

@@ -20,3 +20,4 @@ hyper = { version = "0.14" }
hyper-rustls = { version = "0.24.0", features = ["http2"] }
protoc-bin-vendored = "3.0.0"
uuid = { version = "1.7.0", features = ["v4"] }
tauri = { version = "1.5.4", features = ["process-command-api"]}

View File

@@ -1,23 +1,23 @@
use std::env::temp_dir;
use std::ops::Deref;
use std::path::PathBuf;
use std::process::Command;
use std::str::FromStr;
use anyhow::anyhow;
use hyper::client::HttpConnector;
use hyper::Client;
use hyper::client::HttpConnector;
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
use log::{debug, warn};
use log::{debug, info, warn};
use prost::Message;
use prost_reflect::{DescriptorPool, MethodDescriptor};
use prost_types::{FileDescriptorProto, FileDescriptorSet};
use tauri::api::process::{Command, CommandEvent};
use tokio::fs;
use tokio_stream::StreamExt;
use tonic::body::BoxBody;
use tonic::codegen::http::uri::PathAndQuery;
use tonic::transport::Uri;
use tonic::Request;
use tonic::transport::Uri;
use tonic_reflection::pb::server_reflection_client::ServerReflectionClient;
use tonic_reflection::pb::server_reflection_request::MessageRequest;
use tonic_reflection::pb::server_reflection_response::MessageResponse;
@@ -27,36 +27,67 @@ pub async fn fill_pool_from_files(paths: Vec<PathBuf>) -> Result<DescriptorPool,
let mut pool = DescriptorPool::new();
let random_file_name = format!("{}.desc", uuid::Uuid::new_v4());
let desc_path = temp_dir().join(random_file_name);
let bin = protoc_bin_vendored::protoc_bin_path().unwrap();
let mut cmd = Command::new(bin.clone());
cmd.arg("--include_imports")
.arg("--include_source_info")
.arg("-o")
.arg(&desc_path);
let mut args = vec![
"--include_imports".to_string(),
"--include_source_info".to_string(),
"-o".to_string(),
desc_path.to_string_lossy().to_string(),
];
for p in paths {
if p.as_path().exists() {
cmd.arg(p.as_path().to_string_lossy().as_ref());
args.push(p.to_string_lossy().to_string());
} else {
continue;
}
let parent = p.as_path().parent();
if let Some(parent_path) = parent {
cmd.arg("-I").arg(parent_path);
cmd.arg("-I").arg(parent_path.parent().unwrap());
args.push("-I".to_string());
args.push(parent_path.to_string_lossy().to_string());
args.push("-I".to_string());
args.push(parent_path.parent().unwrap().to_string_lossy().to_string());
} else {
debug!("ignoring {:?} since it does not exist.", parent)
}
}
let output = cmd.output().map_err(|e| e.to_string())?;
if !output.status.success() {
return Err(format!(
"protoc failed: {}",
String::from_utf8_lossy(&output.stderr)
));
let (mut rx, _child) = Command::new_sidecar("protoc")
.expect("protoc not found")
.args(args)
.spawn()
.expect("protoc failed to start");
while let Some(event) = rx.recv().await {
match event {
CommandEvent::Stdout(line) => {
info!("protoc stdout: {}", line);
}
CommandEvent::Stderr(line) => {
info!("protoc stderr: {}", line);
}
CommandEvent::Error(e) => {
return Err(e.to_string());
}
CommandEvent::Terminated(c) => {
match c.code {
Some(0) => {
// success
}
Some(code) => {
return Err(format!(
"protoc failed with exit code: {}",
code,
));
}
None => {
return Err("protoc failed with no exit code".to_string());
}
}
}
_ => {}
};
}
let bytes = fs::read(desc_path.as_path())

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -8,7 +8,7 @@
},
"package": {
"productName": "Yaak",
"version": "2024.3.0-beta.1"
"version": "2024.3.0-beta.2"
},
"tauri": {
"windows": [],
@@ -32,7 +32,13 @@
},
"shell": {
"all": false,
"open": true
"open": true,
"sidecar": true,
"scope": [
{ "name": "protoc", "sidecar": true,
"args": true
}
]
},
"window": {
"close": true,
@@ -56,7 +62,9 @@
"active": true,
"category": "DeveloperTool",
"copyright": "",
"externalBin": [],
"externalBin": [
"protoc-vendored/protoc"
],
"icon": [
"icons/release/32x32.png",
"icons/release/128x128.png",

View File

@@ -8,16 +8,17 @@ import { Dropdown } from './core/Dropdown';
interface Props {
hideFolder?: boolean;
children: DropdownProps['children'];
openOnHotKeyAction?: DropdownProps['openOnHotKeyAction'];
}
export function CreateDropdown({ hideFolder, children }: Props) {
export function CreateDropdown({ hideFolder, children, openOnHotKeyAction }: Props) {
const createHttpRequest = useCreateHttpRequest();
const createGrpcRequest = useCreateGrpcRequest();
const createFolder = useCreateFolder();
return (
<Dropdown
openOnHotKeyAction="http_request.create"
openOnHotKeyAction={openOnHotKeyAction}
items={[
{
key: 'create-http-request',

View File

@@ -1,6 +1,6 @@
import classNames from 'classnames';
import type { ForwardedRef, ReactNode } from 'react';
import React, { Fragment, forwardRef, useCallback, useMemo, useRef, useState } from 'react';
import React, { forwardRef, Fragment, useCallback, useMemo, useRef, useState } from 'react';
import type { XYCoord } from 'react-dnd';
import { useDrag, useDrop } from 'react-dnd';
import { useKey, useKeyPressEvent } from 'react-use';
@@ -11,8 +11,6 @@ import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { useCreateFolder } from '../hooks/useCreateFolder';
import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
import { useDeleteAnyGrpcRequest } from '../hooks/useDeleteAnyGrpcRequest';
import { useDeleteAnyHttpRequest } from '../hooks/useDeleteAnyHttpRequest';
import { useDeleteFolder } from '../hooks/useDeleteFolder';
import { useDeleteRequest } from '../hooks/useDeleteRequest';
import { useDuplicateGrpcRequest } from '../hooks/useDuplicateGrpcRequest';
@@ -68,8 +66,6 @@ export function Sidebar({ className }: Props) {
const httpRequests = useHttpRequests();
const grpcRequests = useGrpcRequests();
const folders = useFolders();
const deleteAnyHttpRequest = useDeleteAnyHttpRequest();
const deleteAnyGrpcRequest = useDeleteAnyGrpcRequest();
const activeWorkspace = useActiveWorkspace();
const duplicateHttpRequest = useDuplicateHttpRequest({
id: activeRequest?.id ?? null,
@@ -108,9 +104,10 @@ export function Sidebar({ className }: Props) {
[collapsed.value],
);
const { tree, treeParentMap, selectableRequests } = useMemo<{
const { tree, treeParentMap, selectableRequests, selectedRequest } = useMemo<{
tree: TreeNode | null;
treeParentMap: Record<string, TreeNode>;
selectedRequest: HttpRequest | GrpcRequest | null;
selectableRequests: {
id: string;
index: number;
@@ -123,14 +120,23 @@ export function Sidebar({ className }: Props) {
index: number;
tree: TreeNode;
}[] = [];
if (activeWorkspace == null) {
return { tree: null, treeParentMap, selectableRequests };
return { tree: null, treeParentMap, selectableRequests, selectedRequest: null };
}
let selectedRequest: HttpRequest | GrpcRequest | null = null;
let selectableRequestIndex = 0;
// Put requests and folders into a tree structure
const next = (node: TreeNode): TreeNode => {
if (
node.item.id === selectedId &&
(node.item.model === 'http_request' || node.item.model === 'grpc_request')
) {
selectedRequest = node.item;
}
const childItems = [...httpRequests, ...grpcRequests, ...folders].filter((f) =>
node.item.model === 'workspace' ? f.folderId == null : f.folderId === node.item.id,
);
@@ -149,8 +155,10 @@ export function Sidebar({ className }: Props) {
const tree = next({ item: activeWorkspace, children: [], depth: 0 });
return { tree, treeParentMap, selectableRequests };
}, [activeWorkspace, httpRequests, grpcRequests, folders]);
return { tree, treeParentMap, selectableRequests, selectedRequest };
}, [activeWorkspace, selectedId, httpRequests, grpcRequests, folders]);
const deleteSelectedRequest = useDeleteRequest(selectedRequest);
const focusActiveRequest = useCallback(
(
@@ -221,16 +229,15 @@ export function Sidebar({ className }: Props) {
const handleBlur = useCallback(() => setHasFocus(false), []);
const handleDeleteKey = useCallback(
(e: KeyboardEvent) => {
async (e: KeyboardEvent) => {
if (!hasFocus) return;
e.preventDefault();
const selected = selectableRequests.find((r) => r.id === selectedId);
if (selected == null) return;
deleteAnyHttpRequest.mutate(selected.id);
deleteAnyGrpcRequest.mutate(selected.id);
await deleteSelectedRequest.mutateAsync();
},
[deleteAnyHttpRequest, deleteAnyGrpcRequest, hasFocus, selectableRequests, selectedId],
[hasFocus, selectableRequests, deleteSelectedRequest, selectedId],
);
useKeyPressEvent('Backspace', handleDeleteKey);
@@ -577,7 +584,7 @@ const SidebarItem = forwardRef(function SidebarItem(
const createRequest = useCreateHttpRequest();
const createFolder = useCreateFolder();
const deleteFolder = useDeleteFolder(itemId);
const deleteRequest = useDeleteRequest(itemId);
const deleteRequest = useDeleteRequest(activeRequest ?? null);
const duplicateHttpRequest = useDuplicateHttpRequest({ id: itemId, navigateAfter: true });
const duplicateGrpcRequest = useDuplicateGrpcRequest({ id: itemId, navigateAfter: true });
const sendRequest = useSendRequest(itemId);

View File

@@ -25,7 +25,7 @@ export const SidebarActions = memo(function SidebarActions() {
hotkeyAction="sidebar.toggle"
icon={hidden ? 'leftPanelHidden' : 'leftPanelVisible'}
/>
<CreateDropdown>
<CreateDropdown openOnHotKeyAction="http_request.create">
<IconButton size="sm" icon="plusCircle" title="Add Resource" />
</CreateDropdown>
</HStack>

View File

@@ -1,11 +1,21 @@
import { useMutation } from '@tanstack/react-query';
import type { HttpRequest } from '../lib/models';
import type { GrpcRequest, HttpRequest } from '../lib/models';
import { useDeleteAnyGrpcRequest } from './useDeleteAnyGrpcRequest';
import { useDeleteAnyHttpRequest } from './useDeleteAnyHttpRequest';
export function useDeleteRequest(id: string | null) {
const deleteAnyRequest = useDeleteAnyHttpRequest();
export function useDeleteRequest(request: HttpRequest | GrpcRequest | null) {
const deleteAnyHttpRequest = useDeleteAnyHttpRequest();
const deleteAnyGrpcRequest = useDeleteAnyGrpcRequest();
return useMutation<HttpRequest | null, string>({
mutationFn: () => deleteAnyRequest.mutateAsync(id ?? 'n/a'),
return useMutation<void, string>({
mutationFn: async () => {
if (request?.model === 'http_request') {
await deleteAnyHttpRequest.mutateAsync(request.id);
} else if (request?.model === 'grpc_request') {
await deleteAnyGrpcRequest.mutateAsync(request.id);
} else {
// Request is null
}
},
});
}