From 33c406ce490bdeef07ac0d03cbfa0bb7fe31c6b7 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Wed, 25 Oct 2023 11:13:00 -0700 Subject: [PATCH] Environments in URL and better rendering --- src-tauri/src/main.rs | 30 ++++---------- src-tauri/src/render.rs | 25 ++++++++++++ src-web/components/AppRouter.tsx | 16 ++++++-- src-web/components/DropMarker.tsx | 4 +- .../components/EnvironmentActionsDropdown.tsx | 12 +++--- src-web/components/EnvironmentEditDialog.tsx | 11 ++++-- src-web/components/Overlay.tsx | 4 +- src-web/components/RequestPane.tsx | 4 +- src-web/components/RequestResponse.tsx | 4 +- src-web/components/ResizeHandle.tsx | 6 +-- src-web/components/ResponseHeaders.tsx | 4 +- src-web/components/ResponsePane.tsx | 6 +-- src-web/components/RouteError.tsx | 3 +- src-web/components/Sidebar.tsx | 22 +++++++---- src-web/components/UrlBar.tsx | 4 +- src-web/components/Workspace.tsx | 10 ++--- .../components/WorkspaceActionsDropdown.tsx | 10 +++-- src-web/components/WorkspaceHeader.tsx | 4 +- src-web/components/core/Banner.tsx | 4 +- src-web/components/core/Button.tsx | 4 +- src-web/components/core/Checkbox.tsx | 4 +- src-web/components/core/CountBadge.tsx | 4 +- src-web/components/core/Dialog.tsx | 4 +- src-web/components/core/Dropdown.tsx | 10 ++--- src-web/components/core/Editor/Editor.tsx | 4 +- src-web/components/core/FormattedError.tsx | 9 ++++- src-web/components/core/Heading.tsx | 4 +- src-web/components/core/HotKey.tsx | 4 +- src-web/components/core/Icon.tsx | 4 +- src-web/components/core/IconButton.tsx | 6 +-- src-web/components/core/InlineCode.tsx | 4 +- src-web/components/core/Input.tsx | 8 ++-- src-web/components/core/PairEditor.tsx | 16 ++++---- src-web/components/core/Separator.tsx | 6 +-- src-web/components/core/Stacks.tsx | 8 ++-- src-web/components/core/StatusTag.tsx | 4 +- src-web/components/core/Tabs/Tabs.tsx | 12 +++--- src-web/components/core/WindowDragRegion.tsx | 4 +- .../components/responseViewers/CsvViewer.tsx | 6 +-- .../responseViewers/ImageViewer.tsx | 4 +- src-web/hooks/useActiveEnvironment.ts | 10 ++--- src-web/hooks/useActiveEnvironmentId.ts | 18 ++++----- src-web/hooks/useAppRoutes.ts | 39 ++++++++++++++++--- src-web/hooks/useCreateEnvironment.ts | 7 +++- 44 files changed, 226 insertions(+), 160 deletions(-) create mode 100644 src-tauri/src/render.rs diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 30774b12..9678f59c 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -7,35 +7,30 @@ #[macro_use] extern crate objc; -use std::collections::HashMap; -use std::env::current_dir; -use std::fs::{create_dir_all, File}; -use std::io::Write; - +use crate::models::{find_environments, generate_id}; use base64::Engine; use http::header::{HeaderName, ACCEPT, USER_AGENT}; use http::{HeaderMap, HeaderValue, Method}; use rand::random; use reqwest::redirect::Policy; use serde::Serialize; -use serde_json::json; use sqlx::migrate::Migrator; use sqlx::sqlite::SqlitePoolOptions; use sqlx::types::{Json, JsonValue}; use sqlx::{Pool, Sqlite}; -use tauri::api::path::data_dir; -use tauri::regex::Regex; +use std::collections::HashMap; +use std::env::current_dir; +use std::fs::{create_dir_all, File}; +use std::io::Write; #[cfg(target_os = "macos")] use tauri::TitleBarStyle; use tauri::{AppHandle, Menu, MenuItem, RunEvent, State, Submenu, Window, WindowUrl, Wry}; use tauri::{CustomMenuItem, Manager, WindowEvent}; use tokio::sync::Mutex; - use window_ext::WindowExt; -use crate::models::{find_environments, generate_id}; - mod models; +mod render; mod window_ext; #[derive(serde::Serialize)] @@ -84,24 +79,13 @@ async fn actually_send_ephemeral_request( pool: &Pool, ) -> Result { let start = std::time::Instant::now(); - let mut url_string = request.url.to_string(); let environments = find_environments(&request.workspace_id, pool) .await .expect("Failed to find environments"); let environment: models::Environment = environments.first().unwrap().clone(); - let variables = environment.data; - let re = Regex::new(r"\$\{\[\s*([^]\s]+)\s*]}").expect("Failed to create regex"); - url_string = re - .replace(&url_string, |caps: &tauri::regex::Captures| { - let key = caps.get(1).unwrap().as_str(); - match variables.get(key) { - Some(v) => v.as_str().unwrap(), - None => "", - } - }) - .to_string(); + let mut url_string = render::render(&request.url, environment.clone()); if !url_string.starts_with("http://") && !url_string.starts_with("https://") { url_string = format!("http://{}", url_string); diff --git a/src-tauri/src/render.rs b/src-tauri/src/render.rs new file mode 100644 index 00000000..df5d3532 --- /dev/null +++ b/src-tauri/src/render.rs @@ -0,0 +1,25 @@ +use tauri::regex::Regex; + +use crate::models::Environment; + +pub fn render(template: &str, environment: Environment) -> String { + let variables = environment.data; + let re = Regex::new(r"\$\{\[\s*([^]\s]+)\s*]}").expect("Failed to create regex"); + let rendered = re + .replace(template, |caps: &tauri::regex::Captures| { + let key = caps.get(1).unwrap().as_str(); + match variables.get(key) { + Some(v) => { + if v.is_string() { + v.as_str().expect("Should be string").to_string() + } else { + v.to_string() + } + } + None => "".to_string(), + } + }) + .to_string(); + + rendered +} diff --git a/src-web/components/AppRouter.tsx b/src-web/components/AppRouter.tsx index e2d1c2f0..47a95ae2 100644 --- a/src-web/components/AppRouter.tsx +++ b/src-web/components/AppRouter.tsx @@ -7,6 +7,7 @@ import RouteError from './RouteError'; import Workspace from './Workspace'; import Workspaces from './Workspaces'; import { DialogProvider } from './DialogContext'; +import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId'; const router = createBrowserRouter([ { @@ -23,12 +24,16 @@ const router = createBrowserRouter([ element: , }, { - path: routePaths.workspace({ workspaceId: ':workspaceId' }), + path: routePaths.workspace({ + workspaceId: ':workspaceId', + environmentId: ':environmentId', + }), element: , }, { path: routePaths.request({ workspaceId: ':workspaceId', + environmentId: ':environmentId', requestId: ':requestId', }), element: , @@ -42,18 +47,23 @@ export function AppRouter() { } function WorkspaceOrRedirect() { + const environmentId = useActiveEnvironmentId(); const recentRequests = useRecentRequests(); const requests = useRequests(); const request = requests.find((r) => r.id === recentRequests[0]); const routes = useAppRoutes(); - if (request === undefined) { + if (request === undefined || environmentId === null) { return ; } return ( ); } diff --git a/src-web/components/DropMarker.tsx b/src-web/components/DropMarker.tsx index 7876e506..eab99577 100644 --- a/src-web/components/DropMarker.tsx +++ b/src-web/components/DropMarker.tsx @@ -1,4 +1,4 @@ -import classnames from 'classnames'; +import classNames from 'classnames'; import React, { memo } from 'react'; interface Props { @@ -9,7 +9,7 @@ export const DropMarker = memo( function DropMarker({ className }: Props) { return (
{ const environmentItems = environments.map( @@ -33,7 +35,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo key: e.id, label: e.name, onSelect: async () => { - setActiveEnvironment(e); + routes.setEnvironment(e); }, }), [], @@ -112,15 +114,15 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo dialog, environments, prompt, - setActiveEnvironment, updateEnvironment, + routes, ]); return ( @@ -149,7 +149,7 @@ export const TabContent = memo(function TabContent({
{children}
diff --git a/src-web/components/core/WindowDragRegion.tsx b/src-web/components/core/WindowDragRegion.tsx index 0c1f310f..23a56617 100644 --- a/src-web/components/core/WindowDragRegion.tsx +++ b/src-web/components/core/WindowDragRegion.tsx @@ -1,4 +1,4 @@ -import classnames from 'classnames'; +import classNames from 'classnames'; import type { ReactNode } from 'react'; interface Props { @@ -10,7 +10,7 @@ export function WindowDragRegion({ className, ...props }: Props) { return (
); diff --git a/src-web/components/responseViewers/CsvViewer.tsx b/src-web/components/responseViewers/CsvViewer.tsx index df84f320..9a49037c 100644 --- a/src-web/components/responseViewers/CsvViewer.tsx +++ b/src-web/components/responseViewers/CsvViewer.tsx @@ -1,4 +1,4 @@ -import classnames from 'classnames'; +import classNames from 'classnames'; import Papa from 'papaparse'; import { useMemo } from 'react'; import { useResponseBodyText } from '../../hooks/useResponseBodyText'; @@ -21,10 +21,10 @@ export function CsvViewer({ response, className }: Props) { return (
- +
{parsed.data.map((row, i) => ( - 0 && 'border-b')}> + 0 && 'border-b')}> {row.map((col, j) => (
{col} diff --git a/src-web/components/responseViewers/ImageViewer.tsx b/src-web/components/responseViewers/ImageViewer.tsx index 278ef85d..2fd7b0ed 100644 --- a/src-web/components/responseViewers/ImageViewer.tsx +++ b/src-web/components/responseViewers/ImageViewer.tsx @@ -1,5 +1,5 @@ import { convertFileSrc } from '@tauri-apps/api/tauri'; -import classnames from 'classnames'; +import classNames from 'classnames'; import type { HttpResponse } from '../../lib/models'; interface Props { @@ -17,7 +17,7 @@ export function ImageViewer({ response, className }: Props) { Response preview ); } diff --git a/src-web/hooks/useActiveEnvironment.ts b/src-web/hooks/useActiveEnvironment.ts index ead801be..0b22d01a 100644 --- a/src-web/hooks/useActiveEnvironment.ts +++ b/src-web/hooks/useActiveEnvironment.ts @@ -3,17 +3,13 @@ import type { Environment } from '../lib/models'; import { useActiveEnvironmentId } from './useActiveEnvironmentId'; import { useEnvironments } from './useEnvironments'; -export function useActiveEnvironment(): [Environment | null, (environment: Environment) => void] { - const [id, setId] = useActiveEnvironmentId(); +export function useActiveEnvironment(): Environment | null { + const id = useActiveEnvironmentId(); const environments = useEnvironments(); const environment = useMemo( () => environments.find((w) => w.id === id) ?? null, [environments, id], ); - const setActiveEnvironment = useCallback((e: Environment) => { - setId(e.id) - }, [setId]); - - return [environment, setActiveEnvironment]; + return environment; } diff --git a/src-web/hooks/useActiveEnvironmentId.ts b/src-web/hooks/useActiveEnvironmentId.ts index 394c1bb0..412ae12a 100644 --- a/src-web/hooks/useActiveEnvironmentId.ts +++ b/src-web/hooks/useActiveEnvironmentId.ts @@ -1,14 +1,12 @@ import { useCallback } from 'react'; -import { useSearchParams } from 'react-router-dom'; +import { useParams, useSearchParams } from 'react-router-dom'; +import type { RouteParamsRequest } from './useAppRoutes'; -export function useActiveEnvironmentId(): [string | null, (id: string) => void] { - const [searchParams, setSearchParams] = useSearchParams(); - const id = searchParams.get('environmentId') ?? null; +export function useActiveEnvironmentId(): string | null { + const { environmentId } = useParams(); + if (environmentId == null || environmentId === '__default__') { + return null; + } - const setId = useCallback((id: string) => { - searchParams.set('environmentId', id) - setSearchParams(searchParams); - }, [searchParams, setSearchParams]) - - return [id, setId]; + return environmentId; } diff --git a/src-web/hooks/useAppRoutes.ts b/src-web/hooks/useAppRoutes.ts index bbbba9eb..85a08acd 100644 --- a/src-web/hooks/useAppRoutes.ts +++ b/src-web/hooks/useAppRoutes.ts @@ -1,8 +1,12 @@ import { useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; +import { useActiveWorkspaceId } from './useActiveWorkspaceId'; +import { useActiveRequestId } from './useActiveRequestId'; +import type { Environment } from '../lib/models'; export type RouteParamsWorkspace = { workspaceId: string; + environmentId: string | null; }; export type RouteParamsRequest = RouteParamsWorkspace & { @@ -13,23 +17,48 @@ export const routePaths = { workspaces() { return '/workspaces'; }, - workspace({ workspaceId } = { workspaceId: ':workspaceId' } as RouteParamsWorkspace) { - return `/workspaces/${workspaceId}`; + workspace( + { workspaceId, environmentId } = { + workspaceId: ':workspaceId', + environmentId: ':environmentId', + } as RouteParamsWorkspace, + ) { + return `/workspaces/${workspaceId}/environments/${environmentId ?? '__default__'}`; }, request( - { workspaceId, requestId } = { + { workspaceId, environmentId, requestId } = { workspaceId: ':workspaceId', + environmentId: ':environmentId', requestId: ':requestId', } as RouteParamsRequest, ) { - return `${this.workspace({ workspaceId })}/requests/${requestId}`; + return `${this.workspace({ workspaceId, environmentId })}/requests/${requestId}`; }, }; export function useAppRoutes() { + const workspaceId = useActiveWorkspaceId(); + const requestId = useActiveRequestId(); + const navigate = useNavigate(); return useMemo( () => ({ + setEnvironment({ id: environmentId }: Environment) { + if (workspaceId == null) { + this.navigate('workspaces'); + } else if (requestId == null) { + this.navigate('workspace', { + workspaceId: workspaceId, + environmentId: environmentId ?? null, + }); + } else { + this.navigate('request', { + workspaceId, + environmentId: environmentId ?? null, + requestId: requestId, + }); + } + }, navigate( path: T, ...params: Parameters<(typeof routePaths)[T]> @@ -42,6 +71,6 @@ export function useAppRoutes() { }, paths: routePaths, }), - [navigate], + [navigate, requestId, workspaceId], ); } diff --git a/src-web/hooks/useCreateEnvironment.ts b/src-web/hooks/useCreateEnvironment.ts index 7aecd3ad..2ba84b61 100644 --- a/src-web/hooks/useCreateEnvironment.ts +++ b/src-web/hooks/useCreateEnvironment.ts @@ -4,18 +4,21 @@ import type { Environment } from '../lib/models'; import { environmentsQueryKey } from './useEnvironments'; import { useActiveWorkspaceId } from './useActiveWorkspaceId'; import { useActiveEnvironmentId } from './useActiveEnvironmentId'; +import { useAppRoutes } from './useAppRoutes'; export function useCreateEnvironment() { + const environmentId = useActiveEnvironmentId(); const workspaceId = useActiveWorkspaceId(); const queryClient = useQueryClient(); - const [, setActiveEnvironmentId ] = useActiveEnvironmentId(); + const routes = useAppRoutes(); + return useMutation>({ mutationFn: (patch) => { return invoke('create_environment', { ...patch, workspaceId }); }, onSuccess: async (environment) => { if (workspaceId == null) return; - setActiveEnvironmentId(environment.id); + routes.navigate('workspace', { workspaceId, environmentId }); queryClient.setQueryData( environmentsQueryKey({ workspaceId }), (environments) => [...(environments ?? []), environment],