mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-20 07:51:20 +02:00
Environments in URL and better rendering
This commit is contained in:
@@ -7,35 +7,30 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate objc;
|
extern crate objc;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use crate::models::{find_environments, generate_id};
|
||||||
use std::env::current_dir;
|
|
||||||
use std::fs::{create_dir_all, File};
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use http::header::{HeaderName, ACCEPT, USER_AGENT};
|
use http::header::{HeaderName, ACCEPT, USER_AGENT};
|
||||||
use http::{HeaderMap, HeaderValue, Method};
|
use http::{HeaderMap, HeaderValue, Method};
|
||||||
use rand::random;
|
use rand::random;
|
||||||
use reqwest::redirect::Policy;
|
use reqwest::redirect::Policy;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::json;
|
|
||||||
use sqlx::migrate::Migrator;
|
use sqlx::migrate::Migrator;
|
||||||
use sqlx::sqlite::SqlitePoolOptions;
|
use sqlx::sqlite::SqlitePoolOptions;
|
||||||
use sqlx::types::{Json, JsonValue};
|
use sqlx::types::{Json, JsonValue};
|
||||||
use sqlx::{Pool, Sqlite};
|
use sqlx::{Pool, Sqlite};
|
||||||
use tauri::api::path::data_dir;
|
use std::collections::HashMap;
|
||||||
use tauri::regex::Regex;
|
use std::env::current_dir;
|
||||||
|
use std::fs::{create_dir_all, File};
|
||||||
|
use std::io::Write;
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use tauri::TitleBarStyle;
|
use tauri::TitleBarStyle;
|
||||||
use tauri::{AppHandle, Menu, MenuItem, RunEvent, State, Submenu, Window, WindowUrl, Wry};
|
use tauri::{AppHandle, Menu, MenuItem, RunEvent, State, Submenu, Window, WindowUrl, Wry};
|
||||||
use tauri::{CustomMenuItem, Manager, WindowEvent};
|
use tauri::{CustomMenuItem, Manager, WindowEvent};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use window_ext::WindowExt;
|
use window_ext::WindowExt;
|
||||||
|
|
||||||
use crate::models::{find_environments, generate_id};
|
|
||||||
|
|
||||||
mod models;
|
mod models;
|
||||||
|
mod render;
|
||||||
mod window_ext;
|
mod window_ext;
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
@@ -84,24 +79,13 @@ async fn actually_send_ephemeral_request(
|
|||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
) -> Result<models::HttpResponse, String> {
|
) -> Result<models::HttpResponse, String> {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
let mut url_string = request.url.to_string();
|
|
||||||
|
|
||||||
let environments = find_environments(&request.workspace_id, pool)
|
let environments = find_environments(&request.workspace_id, pool)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to find environments");
|
.expect("Failed to find environments");
|
||||||
let environment: models::Environment = environments.first().unwrap().clone();
|
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");
|
let mut url_string = render::render(&request.url, environment.clone());
|
||||||
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();
|
|
||||||
|
|
||||||
if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
|
if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
|
||||||
url_string = format!("http://{}", url_string);
|
url_string = format!("http://{}", url_string);
|
||||||
|
|||||||
25
src-tauri/src/render.rs
Normal file
25
src-tauri/src/render.rs
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import RouteError from './RouteError';
|
|||||||
import Workspace from './Workspace';
|
import Workspace from './Workspace';
|
||||||
import Workspaces from './Workspaces';
|
import Workspaces from './Workspaces';
|
||||||
import { DialogProvider } from './DialogContext';
|
import { DialogProvider } from './DialogContext';
|
||||||
|
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@@ -23,12 +24,16 @@ const router = createBrowserRouter([
|
|||||||
element: <Workspaces />,
|
element: <Workspaces />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: routePaths.workspace({ workspaceId: ':workspaceId' }),
|
path: routePaths.workspace({
|
||||||
|
workspaceId: ':workspaceId',
|
||||||
|
environmentId: ':environmentId',
|
||||||
|
}),
|
||||||
element: <WorkspaceOrRedirect />,
|
element: <WorkspaceOrRedirect />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: routePaths.request({
|
path: routePaths.request({
|
||||||
workspaceId: ':workspaceId',
|
workspaceId: ':workspaceId',
|
||||||
|
environmentId: ':environmentId',
|
||||||
requestId: ':requestId',
|
requestId: ':requestId',
|
||||||
}),
|
}),
|
||||||
element: <Workspace />,
|
element: <Workspace />,
|
||||||
@@ -42,18 +47,23 @@ export function AppRouter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function WorkspaceOrRedirect() {
|
function WorkspaceOrRedirect() {
|
||||||
|
const environmentId = useActiveEnvironmentId();
|
||||||
const recentRequests = useRecentRequests();
|
const recentRequests = useRecentRequests();
|
||||||
const requests = useRequests();
|
const requests = useRequests();
|
||||||
const request = requests.find((r) => r.id === recentRequests[0]);
|
const request = requests.find((r) => r.id === recentRequests[0]);
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
|
|
||||||
if (request === undefined) {
|
if (request === undefined || environmentId === null) {
|
||||||
return <Workspace />;
|
return <Workspace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navigate
|
<Navigate
|
||||||
to={routes.paths.request({ workspaceId: request.workspaceId, requestId: request.id })}
|
to={routes.paths.request({
|
||||||
|
workspaceId: request.workspaceId,
|
||||||
|
environmentId,
|
||||||
|
requestId: request.id,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { memo } from 'react';
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -9,7 +9,7 @@ export const DropMarker = memo(
|
|||||||
function DropMarker({ className }: Props) {
|
function DropMarker({ className }: Props) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'relative w-full h-0 overflow-visible pointer-events-none',
|
'relative w-full h-0 overflow-visible pointer-events-none',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import type { DropdownItem } from './core/Dropdown';
|
import type { DropdownItem } from './core/Dropdown';
|
||||||
@@ -12,6 +12,7 @@ import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
|||||||
import { usePrompt } from '../hooks/usePrompt';
|
import { usePrompt } from '../hooks/usePrompt';
|
||||||
import { useDialog } from './DialogContext';
|
import { useDialog } from './DialogContext';
|
||||||
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
|
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
|
||||||
|
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -21,11 +22,12 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
|||||||
className,
|
className,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const environments = useEnvironments();
|
const environments = useEnvironments();
|
||||||
const [activeEnvironment, setActiveEnvironment] = useActiveEnvironment();
|
const activeEnvironment = useActiveEnvironment();
|
||||||
const updateEnvironment = useUpdateEnvironment(activeEnvironment?.id ?? null);
|
const updateEnvironment = useUpdateEnvironment(activeEnvironment?.id ?? null);
|
||||||
const createEnvironment = useCreateEnvironment();
|
const createEnvironment = useCreateEnvironment();
|
||||||
const prompt = usePrompt();
|
const prompt = usePrompt();
|
||||||
const dialog = useDialog();
|
const dialog = useDialog();
|
||||||
|
const routes = useAppRoutes();
|
||||||
|
|
||||||
const items: DropdownItem[] = useMemo(() => {
|
const items: DropdownItem[] = useMemo(() => {
|
||||||
const environmentItems = environments.map(
|
const environmentItems = environments.map(
|
||||||
@@ -33,7 +35,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
|||||||
key: e.id,
|
key: e.id,
|
||||||
label: e.name,
|
label: e.name,
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
setActiveEnvironment(e);
|
routes.setEnvironment(e);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
@@ -112,15 +114,15 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
|||||||
dialog,
|
dialog,
|
||||||
environments,
|
environments,
|
||||||
prompt,
|
prompt,
|
||||||
setActiveEnvironment,
|
|
||||||
updateEnvironment,
|
updateEnvironment,
|
||||||
|
routes,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown items={items}>
|
<Dropdown items={items}>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className={classnames(className, 'text-gray-800 !px-2 truncate')}
|
className={classNames(className, 'text-gray-800 !px-2 truncate')}
|
||||||
forDropdown
|
forDropdown
|
||||||
>
|
>
|
||||||
{activeEnvironment?.name ?? <span className="italic text-gray-500">No Environment</span>}
|
{activeEnvironment?.name ?? <span className="italic text-gray-500">No Environment</span>}
|
||||||
|
|||||||
@@ -5,14 +5,17 @@ import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment';
|
|||||||
import type { Environment } from '../lib/models';
|
import type { Environment } from '../lib/models';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import { Editor } from './core/Editor';
|
import { Editor } from './core/Editor';
|
||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
|
|
||||||
export const EnvironmentEditDialog = function() {
|
export const EnvironmentEditDialog = function() {
|
||||||
|
const routes = useAppRoutes();
|
||||||
const prompt = usePrompt();
|
const prompt = usePrompt();
|
||||||
const environments = useEnvironments();
|
const environments = useEnvironments();
|
||||||
const createEnvironment = useCreateEnvironment();
|
const createEnvironment = useCreateEnvironment();
|
||||||
const [activeEnvironment, setActiveEnvironment] = useActiveEnvironment();
|
const activeEnvironment = useActiveEnvironment();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full grid gap-3 grid-cols-[auto_minmax(0,1fr)]">
|
<div className="h-full grid gap-3 grid-cols-[auto_minmax(0,1fr)]">
|
||||||
@@ -20,14 +23,14 @@ export const EnvironmentEditDialog = function() {
|
|||||||
{environments.map((e) => (
|
{environments.map((e) => (
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className={classnames(
|
className={classNames(
|
||||||
'w-full',
|
'w-full',
|
||||||
activeEnvironment?.id === e.id && 'bg-gray-100 text-gray-1000',
|
activeEnvironment?.id === e.id && 'bg-gray-100 text-gray-1000',
|
||||||
)}
|
)}
|
||||||
justify="start"
|
justify="start"
|
||||||
key={e.id}
|
key={e.id}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveEnvironment(e);
|
routes.setEnvironment(e);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{e.name}
|
{e.name}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import FocusTrap from 'focus-trap-react';
|
import FocusTrap from 'focus-trap-react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
@@ -26,7 +26,7 @@ export function Overlay({ zIndex = 30, open, onClose, portalName, children }: Pr
|
|||||||
{open && (
|
{open && (
|
||||||
<FocusTrap>
|
<FocusTrap>
|
||||||
<motion.div
|
<motion.div
|
||||||
className={classnames('fixed inset-0', zIndexes[zIndex])}
|
className={classNames('fixed inset-0', zIndexes[zIndex])}
|
||||||
initial={{ opacity: 0 }}
|
initial={{ opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import { appWindow } from '@tauri-apps/api/window';
|
import { appWindow } from '@tauri-apps/api/window';
|
||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useCallback, useMemo, useState } from 'react';
|
import { memo, useCallback, useMemo, useState } from 'react';
|
||||||
import { createGlobalState } from 'react-use';
|
import { createGlobalState } from 'react-use';
|
||||||
@@ -152,7 +152,7 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={style}
|
style={style}
|
||||||
className={classnames(className, 'h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1')}
|
className={classNames(className, 'h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1')}
|
||||||
>
|
>
|
||||||
{activeRequest && (
|
{activeRequest && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import useResizeObserver from '@react-hook/resize-observer';
|
import useResizeObserver from '@react-hook/resize-observer';
|
||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import React, { memo, useCallback, useMemo, useRef, useState } from 'react';
|
import React, { memo, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { useLocalStorage } from 'react-use';
|
import { useLocalStorage } from 'react-use';
|
||||||
@@ -120,7 +120,7 @@ export const RequestResponse = memo(function RequestResponse({ style }: Props) {
|
|||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
style={drag}
|
style={drag}
|
||||||
isResizing={isResizing}
|
isResizing={isResizing}
|
||||||
className={classnames(vertical ? 'translate-y-0.5' : 'translate-x-0.5')}
|
className={classNames(vertical ? 'translate-y-0.5' : 'translate-x-0.5')}
|
||||||
onResizeStart={handleResizeStart}
|
onResizeStart={handleResizeStart}
|
||||||
onReset={handleReset}
|
onReset={handleReset}
|
||||||
side={vertical ? 'top' : 'left'}
|
side={vertical ? 'top' : 'left'}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ export function ResizeHandle({
|
|||||||
aria-hidden
|
aria-hidden
|
||||||
draggable
|
draggable
|
||||||
style={style}
|
style={style}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'group z-10 flex',
|
'group z-10 flex',
|
||||||
vertical ? 'w-full h-3 cursor-row-resize' : 'h-full w-3 cursor-col-resize',
|
vertical ? 'w-full h-3 cursor-row-resize' : 'h-full w-3 cursor-col-resize',
|
||||||
@@ -45,7 +45,7 @@ export function ResizeHandle({
|
|||||||
{/* Show global overlay with cursor style to ensure cursor remains the same when moving quickly */}
|
{/* Show global overlay with cursor style to ensure cursor remains the same when moving quickly */}
|
||||||
{isResizing && (
|
{isResizing && (
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classNames(
|
||||||
'fixed -left-20 -right-20 -top-20 -bottom-20',
|
'fixed -left-20 -right-20 -top-20 -bottom-20',
|
||||||
vertical && 'cursor-row-resize',
|
vertical && 'cursor-row-resize',
|
||||||
!vertical && 'cursor-col-resize',
|
!vertical && 'cursor-col-resize',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { HttpResponse } from '../lib/models';
|
import type { HttpResponse } from '../lib/models';
|
||||||
import { HStack } from './core/Stacks';
|
import { HStack } from './core/Stacks';
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ export function ResponseHeaders({ headers }: Props) {
|
|||||||
<HStack
|
<HStack
|
||||||
space={3}
|
space={3}
|
||||||
key={i}
|
key={i}
|
||||||
className={classnames(i > 0 ? 'border-t border-highlightSecondary py-1' : 'pb-1')}
|
className={classNames(i > 0 ? 'border-t border-highlightSecondary py-1' : 'pb-1')}
|
||||||
>
|
>
|
||||||
<dd className="w-1/3 text-violet-600 select-text cursor-text">{h.name}</dd>
|
<dd className="w-1/3 text-violet-600 select-text cursor-text">{h.name}</dd>
|
||||||
<dt className="w-2/3 select-text cursor-text break-all">{h.value}</dt>
|
<dt className="w-2/3 select-text cursor-text break-all">{h.value}</dt>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { useCallback, memo, useEffect, useMemo, useState } from 'react';
|
import { useCallback, memo, useEffect, useMemo, useState } from 'react';
|
||||||
import { createGlobalState } from 'react-use';
|
import { createGlobalState } from 'react-use';
|
||||||
@@ -84,7 +84,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={style}
|
style={style}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'bg-gray-50 max-h-full h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1',
|
'bg-gray-50 max-h-full h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1',
|
||||||
'dark:bg-gray-100 rounded-md border border-highlight',
|
'dark:bg-gray-100 rounded-md border border-highlight',
|
||||||
@@ -96,7 +96,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
|||||||
<>
|
<>
|
||||||
<HStack
|
<HStack
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
className={classnames(
|
className={classNames(
|
||||||
'text-gray-700 text-sm w-full flex-shrink-0',
|
'text-gray-700 text-sm w-full flex-shrink-0',
|
||||||
// Remove a bit of space because the tabs have lots too
|
// Remove a bit of space because the tabs have lots too
|
||||||
'-mb-1.5',
|
'-mb-1.5',
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ import { VStack } from './core/Stacks';
|
|||||||
|
|
||||||
export default function RouteError() {
|
export default function RouteError() {
|
||||||
const error = useRouteError();
|
const error = useRouteError();
|
||||||
|
console.log("Error", error);
|
||||||
const stringified = JSON.stringify(error);
|
const stringified = JSON.stringify(error);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const message = (error as any).message ?? stringified;
|
const message = (error as any).message ?? stringified;
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-full">
|
<div className="flex items-center justify-center h-full">
|
||||||
<VStack space={5} className="max-w-[30rem] !h-auto">
|
<VStack space={5} className="max-w-[50rem] !h-auto">
|
||||||
<Heading>Route Error 🔥</Heading>
|
<Heading>Route Error 🔥</Heading>
|
||||||
<FormattedError>{message}</FormattedError>
|
<FormattedError>{message}</FormattedError>
|
||||||
<VStack space={2}>
|
<VStack space={2}>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ForwardedRef } from 'react';
|
import type { ForwardedRef } from 'react';
|
||||||
import React, { forwardRef, Fragment, memo, useCallback, useMemo, useRef, useState } from 'react';
|
import React, { forwardRef, Fragment, memo, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import type { XYCoord } from 'react-dnd';
|
import type { XYCoord } from 'react-dnd';
|
||||||
@@ -19,6 +19,7 @@ import { Icon } from './core/Icon';
|
|||||||
import { VStack } from './core/Stacks';
|
import { VStack } from './core/Stacks';
|
||||||
import { StatusTag } from './core/StatusTag';
|
import { StatusTag } from './core/StatusTag';
|
||||||
import { DropMarker } from './DropMarker';
|
import { DropMarker } from './DropMarker';
|
||||||
|
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -32,6 +33,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
const { hidden } = useSidebarHidden();
|
const { hidden } = useSidebarHidden();
|
||||||
const sidebarRef = useRef<HTMLDivElement>(null);
|
const sidebarRef = useRef<HTMLDivElement>(null);
|
||||||
const activeRequestId = useActiveRequestId();
|
const activeRequestId = useActiveRequestId();
|
||||||
|
const activeEnvironmentId = useActiveEnvironmentId();
|
||||||
const unorderedRequests = useRequests();
|
const unorderedRequests = useRequests();
|
||||||
const deleteAnyRequest = useDeleteAnyRequest();
|
const deleteAnyRequest = useDeleteAnyRequest();
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
@@ -58,11 +60,15 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
const index = requests.findIndex((r) => r.id === requestId);
|
const index = requests.findIndex((r) => r.id === requestId);
|
||||||
const request = requests[index];
|
const request = requests[index];
|
||||||
if (!request) return;
|
if (!request) return;
|
||||||
routes.navigate('request', { requestId, workspaceId: request.workspaceId });
|
routes.navigate('request', {
|
||||||
|
requestId,
|
||||||
|
workspaceId: request.workspaceId,
|
||||||
|
environmentId: activeEnvironmentId,
|
||||||
|
});
|
||||||
setSelectedIndex(index);
|
setSelectedIndex(index);
|
||||||
focusActiveRequest(index);
|
focusActiveRequest(index);
|
||||||
},
|
},
|
||||||
[focusActiveRequest, requests, routes],
|
[focusActiveRequest, requests, routes, activeEnvironmentId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFocus = useCallback(() => {
|
const handleFocus = useCallback(() => {
|
||||||
@@ -143,7 +149,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
onBlur={handleBlur}
|
onBlur={handleBlur}
|
||||||
tabIndex={hidden ? -1 : 0}
|
tabIndex={hidden ? -1 : 0}
|
||||||
className={classnames(className, 'h-full relative grid grid-rows-[minmax(0,1fr)_auto]')}
|
className={classNames(className, 'h-full relative grid grid-rows-[minmax(0,1fr)_auto]')}
|
||||||
>
|
>
|
||||||
<VStack
|
<VStack
|
||||||
as="ul"
|
as="ul"
|
||||||
@@ -299,7 +305,7 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
|||||||
}, [onSelect, requestId]);
|
}, [onSelect, requestId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li ref={ref} className={classnames(className, 'block group/item px-2 pb-0.5')}>
|
<li ref={ref} className={classNames(className, 'block group/item px-2 pb-0.5')}>
|
||||||
<button
|
<button
|
||||||
// tabIndex={-1} // Will prevent drag-n-drop
|
// tabIndex={-1} // Will prevent drag-n-drop
|
||||||
onClick={handleSelect}
|
onClick={handleSelect}
|
||||||
@@ -307,7 +313,7 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
|||||||
onDoubleClick={handleStartEditing}
|
onDoubleClick={handleStartEditing}
|
||||||
data-active={isActive}
|
data-active={isActive}
|
||||||
data-selected={selected}
|
data-selected={selected}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
'w-full flex items-center text-sm h-xs px-2 rounded-md transition-colors',
|
'w-full flex items-center text-sm h-xs px-2 rounded-md transition-colors',
|
||||||
editing && 'ring-1 focus-within:ring-focus',
|
editing && 'ring-1 focus-within:ring-focus',
|
||||||
isActive && 'bg-highlight text-gray-800',
|
isActive && 'bg-highlight text-gray-800',
|
||||||
@@ -324,7 +330,7 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
|||||||
onKeyDown={handleInputKeyDown}
|
onKeyDown={handleInputKeyDown}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span className={classnames('truncate', !requestName && 'text-gray-400 italic')}>
|
<span className={classNames('truncate', !requestName && 'text-gray-400 italic')}>
|
||||||
{requestName || 'New Request'}
|
{requestName || 'New Request'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -396,7 +402,7 @@ const DraggableSidebarItem = memo(function DraggableSidebarItem({
|
|||||||
<SidebarItem
|
<SidebarItem
|
||||||
ref={ref}
|
ref={ref}
|
||||||
draggable
|
draggable
|
||||||
className={classnames(isDragging && 'opacity-20')}
|
className={classNames(isDragging && 'opacity-20')}
|
||||||
requestName={requestName}
|
requestName={requestName}
|
||||||
requestId={requestId}
|
requestId={requestId}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { EditorView } from 'codemirror';
|
import type { EditorView } from 'codemirror';
|
||||||
import type { FormEvent } from 'react';
|
import type { FormEvent } from 'react';
|
||||||
import { memo, useCallback, useRef } from 'react';
|
import { memo, useCallback, useRef } from 'react';
|
||||||
@@ -44,7 +44,7 @@ export const UrlBar = memo(function UrlBar({ id: requestId, url, method, classNa
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className={classnames('url-bar', className)}>
|
<form onSubmit={handleSubmit} className={classNames('url-bar', className)}>
|
||||||
<Input
|
<Input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import type {
|
import type {
|
||||||
CSSProperties,
|
CSSProperties,
|
||||||
@@ -113,7 +113,7 @@ export default function Workspace() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={styles}
|
style={styles}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
'grid w-full h-full',
|
'grid w-full h-full',
|
||||||
// Animate sidebar width changes but only when not resizing
|
// Animate sidebar width changes but only when not resizing
|
||||||
// because it's too slow to animate on mouse move
|
// because it's too slow to animate on mouse move
|
||||||
@@ -125,7 +125,7 @@ export default function Workspace() {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, x: -10 }}
|
initial={{ opacity: 0, x: -10 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ opacity: 1, x: 0 }}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
'absolute top-0 left-0 bottom-0 bg-gray-100 border-r border-highlight w-[14rem]',
|
'absolute top-0 left-0 bottom-0 bg-gray-100 border-r border-highlight w-[14rem]',
|
||||||
'grid grid-rows-[auto_1fr]',
|
'grid grid-rows-[auto_1fr]',
|
||||||
)}
|
)}
|
||||||
@@ -140,7 +140,7 @@ export default function Workspace() {
|
|||||||
</Overlay>
|
</Overlay>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div style={side} className={classnames('overflow-hidden bg-gray-100')}>
|
<div style={side} className={classNames('overflow-hidden bg-gray-100')}>
|
||||||
<Sidebar className="border-r border-highlight" />
|
<Sidebar className="border-r border-highlight" />
|
||||||
</div>
|
</div>
|
||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
@@ -173,7 +173,7 @@ function HeaderSize({ className, ...props }: HeaderSizeProps) {
|
|||||||
const platform = useOsInfo();
|
const platform = useOsInfo();
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'h-md pt-[1px] flex items-center w-full pr-3 pl-20 border-b',
|
'h-md pt-[1px] flex items-center w-full pr-3 pl-20 border-b',
|
||||||
platform?.osType === 'Darwin' && 'pl-20',
|
platform?.osType === 'Darwin' && 'pl-20',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
@@ -15,6 +15,7 @@ import { Icon } from './core/Icon';
|
|||||||
import { InlineCode } from './core/InlineCode';
|
import { InlineCode } from './core/InlineCode';
|
||||||
import { HStack } from './core/Stacks';
|
import { HStack } from './core/Stacks';
|
||||||
import { useDialog } from './DialogContext';
|
import { useDialog } from './DialogContext';
|
||||||
|
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -24,6 +25,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
const workspaces = useWorkspaces();
|
const workspaces = useWorkspaces();
|
||||||
const activeWorkspace = useActiveWorkspace();
|
const activeWorkspace = useActiveWorkspace();
|
||||||
const activeWorkspaceId = activeWorkspace?.id ?? null;
|
const activeWorkspaceId = activeWorkspace?.id ?? null;
|
||||||
|
const environmentId = useActiveEnvironmentId();
|
||||||
const createWorkspace = useCreateWorkspace({ navigateAfter: true });
|
const createWorkspace = useCreateWorkspace({ navigateAfter: true });
|
||||||
const updateWorkspace = useUpdateWorkspace(activeWorkspaceId);
|
const updateWorkspace = useUpdateWorkspace(activeWorkspaceId);
|
||||||
const deleteWorkspace = useDeleteWorkspace(activeWorkspace);
|
const deleteWorkspace = useDeleteWorkspace(activeWorkspace);
|
||||||
@@ -53,7 +55,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
color="gray"
|
color="gray"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
hide();
|
hide();
|
||||||
routes.navigate('workspace', { workspaceId: w.id });
|
routes.navigate('workspace', { workspaceId: w.id, environmentId });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
This Window
|
This Window
|
||||||
@@ -66,7 +68,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
hide();
|
hide();
|
||||||
await invoke('new_window', {
|
await invoke('new_window', {
|
||||||
url: routes.paths.workspace({ workspaceId: w.id }),
|
url: routes.paths.workspace({ workspaceId: w.id, environmentId }),
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -150,7 +152,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
|||||||
<Dropdown items={items}>
|
<Dropdown items={items}>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
className={classnames(className, 'text-gray-800 !px-2 truncate')}
|
className={classNames(className, 'text-gray-800 !px-2 truncate')}
|
||||||
forDropdown
|
forDropdown
|
||||||
>
|
>
|
||||||
{activeWorkspace?.name}
|
{activeWorkspace?.name}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { IconButton } from './core/IconButton';
|
import { IconButton } from './core/IconButton';
|
||||||
@@ -20,7 +20,7 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
|
|||||||
<HStack
|
<HStack
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
className={classnames(className, 'w-full h-full')}
|
className={classNames(className, 'w-full h-full')}
|
||||||
>
|
>
|
||||||
<HStack space={0.5} className="flex-1 pointer-events-none" alignItems="center">
|
<HStack space={0.5} className="flex-1 pointer-events-none" alignItems="center">
|
||||||
<SidebarActions />
|
<SidebarActions />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -9,7 +9,7 @@ export function Banner({ children, className }: Props) {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'border border-red-500 bg-red-300/10 text-red-800 px-3 py-2 rounded select-auto cursor-text',
|
'border border-red-500 bg-red-300/10 text-red-800 px-3 py-2 rounded select-auto cursor-text',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { HTMLAttributes, ReactNode } from 'react';
|
import type { HTMLAttributes, ReactNode } from 'react';
|
||||||
import { forwardRef, memo, useMemo } from 'react';
|
import { forwardRef, memo, useMemo } from 'react';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
@@ -45,7 +45,7 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
|||||||
) {
|
) {
|
||||||
const classes = useMemo(
|
const classes = useMemo(
|
||||||
() =>
|
() =>
|
||||||
classnames(
|
classNames(
|
||||||
className,
|
className,
|
||||||
'flex-shrink-0 outline-none whitespace-nowrap',
|
'flex-shrink-0 outline-none whitespace-nowrap',
|
||||||
'focus-visible-or-class:ring',
|
'focus-visible-or-class:ring',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export function Checkbox({ checked, onChange, className, disabled }: Props) {
|
|||||||
aria-checked={checked ? 'true' : 'false'}
|
aria-checked={checked ? 'true' : 'false'}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'flex-shrink-0 w-4 h-4 border border-gray-200 rounded',
|
'flex-shrink-0 w-4 h-4 border border-gray-200 rounded',
|
||||||
'focus:border-focus',
|
'focus:border-focus',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
count: number;
|
count: number;
|
||||||
@@ -10,7 +10,7 @@ export function CountBadge({ count, className }: Props) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'opacity-70 border border-highlight text-3xs rounded mb-0.5 px-1 ml-1 h-4 font-mono',
|
'opacity-70 border border-highlight text-3xs rounded mb-0.5 px-1 ml-1 h-4 font-mono',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@@ -51,7 +51,7 @@ export function Dialog({
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ top: 5, scale: 0.97 }}
|
initial={{ top: 5, scale: 0.97 }}
|
||||||
animate={{ top: 0, scale: 1 }}
|
animate={{ top: 0, scale: 1 }}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'gap-2 grid grid-rows-[auto_minmax(0,1fr)]',
|
'gap-2 grid grid-rows-[auto_minmax(0,1fr)]',
|
||||||
'relative bg-gray-50 pointer-events-auto',
|
'relative bg-gray-50 pointer-events-auto',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import FocusTrap from 'focus-trap-react';
|
import FocusTrap from 'focus-trap-react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import type { CSSProperties, HTMLAttributes, MouseEvent, ReactElement, ReactNode } from 'react';
|
import type { CSSProperties, HTMLAttributes, MouseEvent, ReactElement, ReactNode } from 'react';
|
||||||
@@ -278,7 +278,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
|||||||
dir="ltr"
|
dir="ltr"
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
style={containerStyles}
|
style={containerStyles}
|
||||||
className={classnames(className, 'outline-none mt-1 pointer-events-auto fixed z-50')}
|
className={classNames(className, 'outline-none mt-1 pointer-events-auto fixed z-50')}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-hidden
|
aria-hidden
|
||||||
@@ -290,7 +290,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
|||||||
space={0.5}
|
space={0.5}
|
||||||
ref={initMenu}
|
ref={initMenu}
|
||||||
style={menuStyles}
|
style={menuStyles}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'h-auto bg-gray-50 rounded-md shadow-lg dark:shadow-gray-0 py-1.5 border',
|
'h-auto bg-gray-50 rounded-md shadow-lg dark:shadow-gray-0 py-1.5 border',
|
||||||
'border-gray-200 overflow-auto mb-1 mx-0.5',
|
'border-gray-200 overflow-auto mb-1 mx-0.5',
|
||||||
@@ -356,7 +356,7 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
|||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
justify="start"
|
justify="start"
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'min-w-[8rem] outline-none px-2 mx-1.5 flex text-sm text-gray-700 whitespace-nowrap',
|
'min-w-[8rem] outline-none px-2 mx-1.5 flex text-sm text-gray-700 whitespace-nowrap',
|
||||||
'focus:bg-highlight focus:text-gray-900 rounded',
|
'focus:bg-highlight focus:text-gray-900 rounded',
|
||||||
@@ -366,7 +366,7 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
|||||||
>
|
>
|
||||||
{item.leftSlot && <div className="pr-2 flex justify-start">{item.leftSlot}</div>}
|
{item.leftSlot && <div className="pr-2 flex justify-start">{item.leftSlot}</div>}
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classNames(
|
||||||
// Add padding on right when no right slot, for some visual balance
|
// Add padding on right when no right slot, for some visual balance
|
||||||
!item.rightSlot && 'pr-4',
|
!item.rightSlot && 'pr-4',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { defaultKeymap } from '@codemirror/commands';
|
|||||||
import { Compartment, EditorState, Transaction } from '@codemirror/state';
|
import { Compartment, EditorState, Transaction } from '@codemirror/state';
|
||||||
import type { ViewUpdate } from '@codemirror/view';
|
import type { ViewUpdate } from '@codemirror/view';
|
||||||
import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
|
import { keymap, placeholder as placeholderExt, tooltips } from '@codemirror/view';
|
||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { EditorView } from 'codemirror';
|
import { EditorView } from 'codemirror';
|
||||||
import type { MutableRefObject, ReactNode } from 'react';
|
import type { MutableRefObject, ReactNode } from 'react';
|
||||||
import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef } from 'react';
|
import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef } from 'react';
|
||||||
@@ -168,7 +168,7 @@ const _Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
|
|||||||
const cmContainer = (
|
const cmContainer = (
|
||||||
<div
|
<div
|
||||||
ref={initEditorRef}
|
ref={initEditorRef}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'cm-wrapper text-base bg-gray-50',
|
'cm-wrapper text-base bg-gray-50',
|
||||||
type === 'password' && 'cm-obscure-text',
|
type === 'password' && 'cm-obscure-text',
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: string;
|
children: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FormattedError({ children }: Props) {
|
export function FormattedError({ children }: Props) {
|
||||||
return (
|
return (
|
||||||
<pre className="text-sm select-auto cursor-text bg-gray-100 p-3 rounded whitespace-normal border border-red-500 border-dashed">
|
<pre
|
||||||
|
className={classNames(
|
||||||
|
'text-sm select-auto cursor-text bg-gray-100 p-3 rounded',
|
||||||
|
'whitespace-normal border border-red-500 border-dashed',
|
||||||
|
)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</pre>
|
</pre>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { HTMLAttributes } from 'react';
|
import type { HTMLAttributes } from 'react';
|
||||||
|
|
||||||
export function Heading({ className, children, ...props }: HTMLAttributes<HTMLHeadingElement>) {
|
export function Heading({ className, children, ...props }: HTMLAttributes<HTMLHeadingElement>) {
|
||||||
return (
|
return (
|
||||||
<h1 className={classnames(className, 'text-2xl font-semibold text-gray-900 mb-3')} {...props}>
|
<h1 className={classNames(className, 'text-2xl font-semibold text-gray-900 mb-3')} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</h1>
|
</h1>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modifier: 'Meta' | 'Control' | 'Shift';
|
modifier: 'Meta' | 'Control' | 'Shift';
|
||||||
@@ -13,7 +13,7 @@ const keys: Record<Props['modifier'], string> = {
|
|||||||
|
|
||||||
export function HotKey({ modifier, keyName }: Props) {
|
export function HotKey({ modifier, keyName }: Props) {
|
||||||
return (
|
return (
|
||||||
<span className={classnames('text-sm text-gray-600')}>
|
<span className={classNames('text-sm text-gray-600')}>
|
||||||
{keys[modifier]}
|
{keys[modifier]}
|
||||||
{keyName}
|
{keyName}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import {
|
|||||||
TriangleRightIcon,
|
TriangleRightIcon,
|
||||||
UpdateIcon,
|
UpdateIcon,
|
||||||
} from '@radix-ui/react-icons';
|
} from '@radix-ui/react-icons';
|
||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { HTMLAttributes } from 'react';
|
import type { HTMLAttributes } from 'react';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { ReactComponent as LeftPanelHiddenIcon } from '../../assets/icons/LeftPanelHiddenIcon.svg';
|
import { ReactComponent as LeftPanelHiddenIcon } from '../../assets/icons/LeftPanelHiddenIcon.svg';
|
||||||
@@ -95,7 +95,7 @@ export const Icon = memo(function Icon({ icon, spin, size = 'md', className }: I
|
|||||||
const Component = icons[icon] ?? icons.question;
|
const Component = icons[icon] ?? icons.question;
|
||||||
return (
|
return (
|
||||||
<Component
|
<Component
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'text-inherit',
|
'text-inherit',
|
||||||
size === 'md' && 'h-4 w-4',
|
size === 'md' && 'h-4 w-4',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { MouseEvent } from 'react';
|
import type { MouseEvent } from 'react';
|
||||||
import { forwardRef, useCallback } from 'react';
|
import { forwardRef, useCallback } from 'react';
|
||||||
import { useTimedBoolean } from '../../hooks/useTimedBoolean';
|
import { useTimedBoolean } from '../../hooks/useTimedBoolean';
|
||||||
@@ -45,7 +45,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
|
|||||||
disabled={icon === 'empty'}
|
disabled={icon === 'empty'}
|
||||||
tabIndex={tabIndex ?? icon === 'empty' ? -1 : undefined}
|
tabIndex={tabIndex ?? icon === 'empty' ? -1 : undefined}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'flex-shrink-0 text-gray-700 hover:text-gray-1000',
|
'flex-shrink-0 text-gray-700 hover:text-gray-1000',
|
||||||
'!px-0',
|
'!px-0',
|
||||||
@@ -60,7 +60,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
|
|||||||
size={iconSize}
|
size={iconSize}
|
||||||
icon={confirmed ? 'check' : icon}
|
icon={confirmed ? 'check' : icon}
|
||||||
spin={spin}
|
spin={spin}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
iconClassName,
|
iconClassName,
|
||||||
props.disabled && 'opacity-70',
|
props.disabled && 'opacity-70',
|
||||||
confirmed && 'text-green-600',
|
confirmed && 'text-green-600',
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { HTMLAttributes } from 'react';
|
import type { HTMLAttributes } from 'react';
|
||||||
|
|
||||||
export function InlineCode({ className, ...props }: HTMLAttributes<HTMLSpanElement>) {
|
export function InlineCode({ className, ...props }: HTMLAttributes<HTMLSpanElement>) {
|
||||||
return (
|
return (
|
||||||
<code
|
<code
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'font-mono text-sm bg-highlight border-0 border-gray-200 px-1.5 py-0.5 rounded text-gray-800 shadow-inner',
|
'font-mono text-sm bg-highlight border-0 border-gray-200 px-1.5 py-0.5 rounded text-gray-800 shadow-inner',
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { EditorView } from 'codemirror';
|
import type { EditorView } from 'codemirror';
|
||||||
import type { HTMLAttributes, ReactNode } from 'react';
|
import type { HTMLAttributes, ReactNode } from 'react';
|
||||||
import { forwardRef, useCallback, useMemo, useState } from 'react';
|
import { forwardRef, useCallback, useMemo, useState } from 'react';
|
||||||
@@ -68,7 +68,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
|||||||
}, [onBlur]);
|
}, [onBlur]);
|
||||||
|
|
||||||
const id = `input-${name}`;
|
const id = `input-${name}`;
|
||||||
const inputClassName = classnames(
|
const inputClassName = classNames(
|
||||||
className,
|
className,
|
||||||
'!bg-transparent min-w-0 h-full w-full focus:outline-none placeholder:text-placeholder',
|
'!bg-transparent min-w-0 h-full w-full focus:outline-none placeholder:text-placeholder',
|
||||||
// Bump things over if the slots are occupied
|
// Bump things over if the slots are occupied
|
||||||
@@ -94,7 +94,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
|||||||
<VStack className="w-full">
|
<VStack className="w-full">
|
||||||
<label
|
<label
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
labelClassName,
|
labelClassName,
|
||||||
'font-semibold text-xs uppercase text-gray-700',
|
'font-semibold text-xs uppercase text-gray-700',
|
||||||
hideLabel && 'sr-only',
|
hideLabel && 'sr-only',
|
||||||
@@ -104,7 +104,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
|||||||
</label>
|
</label>
|
||||||
<HStack
|
<HStack
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
className={classnames(
|
className={classNames(
|
||||||
containerClassName,
|
containerClassName,
|
||||||
'relative w-full rounded-md text-gray-900',
|
'relative w-full rounded-md text-gray-900',
|
||||||
'border',
|
'border',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import type { XYCoord } from 'react-dnd';
|
import type { XYCoord } from 'react-dnd';
|
||||||
import { useDrag, useDrop } from 'react-dnd';
|
import { useDrag, useDrop } from 'react-dnd';
|
||||||
@@ -134,7 +134,7 @@ export const PairEditor = memo(function PairEditor({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'@container',
|
'@container',
|
||||||
'pb-2 grid overflow-auto max-h-full',
|
'pb-2 grid overflow-auto max-h-full',
|
||||||
@@ -264,7 +264,7 @@ const FormRow = memo(function FormRow({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'group grid grid-cols-[auto_auto_minmax(0,1fr)_auto]',
|
'group grid grid-cols-[auto_auto_minmax(0,1fr)_auto]',
|
||||||
'grid-rows-1 items-center',
|
'grid-rows-1 items-center',
|
||||||
@@ -273,7 +273,7 @@ const FormRow = memo(function FormRow({
|
|||||||
>
|
>
|
||||||
{!isLast ? (
|
{!isLast ? (
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classNames(
|
||||||
'py-2 h-7 w-3 flex items-center',
|
'py-2 h-7 w-3 flex items-center',
|
||||||
'justify-center opacity-0 hover:opacity-100',
|
'justify-center opacity-0 hover:opacity-100',
|
||||||
)}
|
)}
|
||||||
@@ -286,11 +286,11 @@ const FormRow = memo(function FormRow({
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
disabled={isLast}
|
disabled={isLast}
|
||||||
checked={isLast ? false : !!pairContainer.pair.enabled}
|
checked={isLast ? false : !!pairContainer.pair.enabled}
|
||||||
className={classnames('mr-2', isLast && '!opacity-disabled')}
|
className={classNames('mr-2', isLast && '!opacity-disabled')}
|
||||||
onChange={handleChangeEnabled}
|
onChange={handleChangeEnabled}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classNames(
|
||||||
'grid items-center',
|
'grid items-center',
|
||||||
'@xs:gap-2 @xs:!grid-rows-1 @xs:!grid-cols-[minmax(0,1fr)_minmax(0,1fr)]',
|
'@xs:gap-2 @xs:!grid-rows-1 @xs:!grid-cols-[minmax(0,1fr)_minmax(0,1fr)]',
|
||||||
'gap-0.5 grid-cols-1 grid-rows-2',
|
'gap-0.5 grid-cols-1 grid-rows-2',
|
||||||
@@ -303,7 +303,7 @@ const FormRow = memo(function FormRow({
|
|||||||
validate={nameValidate}
|
validate={nameValidate}
|
||||||
useTemplating
|
useTemplating
|
||||||
forceUpdateKey={forceUpdateKey}
|
forceUpdateKey={forceUpdateKey}
|
||||||
containerClassName={classnames(isLast && 'border-dashed')}
|
containerClassName={classNames(isLast && 'border-dashed')}
|
||||||
defaultValue={pairContainer.pair.name}
|
defaultValue={pairContainer.pair.name}
|
||||||
label="Name"
|
label="Name"
|
||||||
name="name"
|
name="name"
|
||||||
@@ -315,7 +315,7 @@ const FormRow = memo(function FormRow({
|
|||||||
<Input
|
<Input
|
||||||
hideLabel
|
hideLabel
|
||||||
size="sm"
|
size="sm"
|
||||||
containerClassName={classnames(isLast && 'border-dashed')}
|
containerClassName={classNames(isLast && 'border-dashed')}
|
||||||
validate={valueValidate}
|
validate={valueValidate}
|
||||||
forceUpdateKey={forceUpdateKey}
|
forceUpdateKey={forceUpdateKey}
|
||||||
defaultValue={pairContainer.pair.value}
|
defaultValue={pairContainer.pair.value}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
orientation?: 'horizontal' | 'vertical';
|
orientation?: 'horizontal' | 'vertical';
|
||||||
@@ -14,10 +14,10 @@ export function Separator({
|
|||||||
label,
|
label,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
return (
|
return (
|
||||||
<div role="separator" className={classnames(className, 'flex items-center')}>
|
<div role="separator" className={classNames(className, 'flex items-center')}>
|
||||||
{label && <div className="text-xs text-gray-500 mx-2 whitespace-nowrap">{label}</div>}
|
{label && <div className="text-xs text-gray-500 mx-2 whitespace-nowrap">{label}</div>}
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classNames(
|
||||||
variant === 'primary' && 'bg-highlight',
|
variant === 'primary' && 'bg-highlight',
|
||||||
variant === 'secondary' && 'bg-highlightSecondary',
|
variant === 'secondary' && 'bg-highlightSecondary',
|
||||||
orientation === 'horizontal' && 'w-full h-[1px]',
|
orientation === 'horizontal' && 'w-full h-[1px]',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ComponentType, ForwardedRef, HTMLAttributes, ReactNode } from 'react';
|
import type { ComponentType, ForwardedRef, HTMLAttributes, ReactNode } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ export const HStack = forwardRef(function HStack(
|
|||||||
return (
|
return (
|
||||||
<BaseStack
|
<BaseStack
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={classnames(className, 'flex-row', space != null && gapClasses[space])}
|
className={classNames(className, 'flex-row', space != null && gapClasses[space])}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -45,7 +45,7 @@ export const VStack = forwardRef(function VStack(
|
|||||||
return (
|
return (
|
||||||
<BaseStack
|
<BaseStack
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={classnames(className, 'flex-col', space != null && gapClasses[space])}
|
className={classNames(className, 'flex-col', space != null && gapClasses[space])}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -69,7 +69,7 @@ const BaseStack = forwardRef(function BaseStack(
|
|||||||
return (
|
return (
|
||||||
<Component
|
<Component
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'flex',
|
'flex',
|
||||||
alignItems === 'center' && 'items-center',
|
alignItems === 'center' && 'items-center',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { HttpResponse } from '../../lib/models';
|
import type { HttpResponse } from '../../lib/models';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -12,7 +12,7 @@ export function StatusTag({ response, className, showReason }: Props) {
|
|||||||
const label = error ? 'ERR' : status;
|
const label = error ? 'ERR' : status;
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={classnames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'font-mono',
|
'font-mono',
|
||||||
status >= 0 && status < 100 && 'text-red-600',
|
status >= 0 && status < 100 && 'text-red-600',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { memo, useCallback, useEffect, useRef } from 'react';
|
import { memo, useCallback, useEffect, useRef } from 'react';
|
||||||
import { Button } from '../Button';
|
import { Button } from '../Button';
|
||||||
@@ -67,11 +67,11 @@ export function Tabs({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={classnames(className, 'h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1')}
|
className={classNames(className, 'h-full grid grid-rows-[auto_minmax(0,1fr)] grid-cols-1')}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
className={classnames(
|
className={classNames(
|
||||||
tabListClassName,
|
tabListClassName,
|
||||||
'flex items-center overflow-x-auto overflow-y-visible hide-scrollbars mt-1 mb-2',
|
'flex items-center overflow-x-auto overflow-y-visible hide-scrollbars mt-1 mb-2',
|
||||||
// Give space for button focus states within overflow boundary.
|
// Give space for button focus states within overflow boundary.
|
||||||
@@ -81,7 +81,7 @@ export function Tabs({
|
|||||||
<HStack space={2} className="flex-shrink-0">
|
<HStack space={2} className="flex-shrink-0">
|
||||||
{tabs.map((t) => {
|
{tabs.map((t) => {
|
||||||
const isActive = t.value === value;
|
const isActive = t.value === value;
|
||||||
const btnClassName = classnames(
|
const btnClassName = classNames(
|
||||||
isActive ? '' : 'text-gray-600 hover:text-gray-800',
|
isActive ? '' : 'text-gray-600 hover:text-gray-800',
|
||||||
'!px-2 ml-[1px]',
|
'!px-2 ml-[1px]',
|
||||||
);
|
);
|
||||||
@@ -108,7 +108,7 @@ export function Tabs({
|
|||||||
: option?.label ?? 'Unknown'}
|
: option?.label ?? 'Unknown'}
|
||||||
<Icon
|
<Icon
|
||||||
icon="triangleDown"
|
icon="triangleDown"
|
||||||
className={classnames('-mr-1.5', isActive ? 'opacity-100' : 'opacity-20')}
|
className={classNames('-mr-1.5', isActive ? 'opacity-100' : 'opacity-20')}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</RadioDropdown>
|
</RadioDropdown>
|
||||||
@@ -149,7 +149,7 @@ export const TabContent = memo(function TabContent({
|
|||||||
<div
|
<div
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
data-tab={value}
|
data-tab={value}
|
||||||
className={classnames(className, 'tab-content', 'hidden w-full h-full')}
|
className={classNames(className, 'tab-content', 'hidden w-full h-full')}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -10,7 +10,7 @@ export function WindowDragRegion({ className, ...props }: Props) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
className={classnames(className, 'w-full flex-shrink-0')}
|
className={classNames(className, 'w-full flex-shrink-0')}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Papa from 'papaparse';
|
import Papa from 'papaparse';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useResponseBodyText } from '../../hooks/useResponseBodyText';
|
import { useResponseBodyText } from '../../hooks/useResponseBodyText';
|
||||||
@@ -21,10 +21,10 @@ export function CsvViewer({ response, className }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-auto h-full">
|
<div className="overflow-auto h-full">
|
||||||
<table className={classnames(className, 'text-sm')}>
|
<table className={classNames(className, 'text-sm')}>
|
||||||
<tbody>
|
<tbody>
|
||||||
{parsed.data.map((row, i) => (
|
{parsed.data.map((row, i) => (
|
||||||
<tr key={i} className={classnames('border-l border-t', i > 0 && 'border-b')}>
|
<tr key={i} className={classNames('border-l border-t', i > 0 && 'border-b')}>
|
||||||
{row.map((col, j) => (
|
{row.map((col, j) => (
|
||||||
<td key={j} className="border-r px-1.5">
|
<td key={j} className="border-r px-1.5">
|
||||||
{col}
|
{col}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { convertFileSrc } from '@tauri-apps/api/tauri';
|
import { convertFileSrc } from '@tauri-apps/api/tauri';
|
||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { HttpResponse } from '../../lib/models';
|
import type { HttpResponse } from '../../lib/models';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -17,7 +17,7 @@ export function ImageViewer({ response, className }: Props) {
|
|||||||
<img
|
<img
|
||||||
src={src}
|
src={src}
|
||||||
alt="Response preview"
|
alt="Response preview"
|
||||||
className={classnames(className, 'max-w-full max-h-full')}
|
className={classNames(className, 'max-w-full max-h-full')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,13 @@ import type { Environment } from '../lib/models';
|
|||||||
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
||||||
import { useEnvironments } from './useEnvironments';
|
import { useEnvironments } from './useEnvironments';
|
||||||
|
|
||||||
export function useActiveEnvironment(): [Environment | null, (environment: Environment) => void] {
|
export function useActiveEnvironment(): Environment | null {
|
||||||
const [id, setId] = useActiveEnvironmentId();
|
const id = useActiveEnvironmentId();
|
||||||
const environments = useEnvironments();
|
const environments = useEnvironments();
|
||||||
const environment = useMemo(
|
const environment = useMemo(
|
||||||
() => environments.find((w) => w.id === id) ?? null,
|
() => environments.find((w) => w.id === id) ?? null,
|
||||||
[environments, id],
|
[environments, id],
|
||||||
);
|
);
|
||||||
|
|
||||||
const setActiveEnvironment = useCallback((e: Environment) => {
|
return environment;
|
||||||
setId(e.id)
|
|
||||||
}, [setId]);
|
|
||||||
|
|
||||||
return [environment, setActiveEnvironment];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { useCallback } from 'react';
|
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] {
|
export function useActiveEnvironmentId(): string | null {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const { environmentId } = useParams<RouteParamsRequest>();
|
||||||
const id = searchParams.get('environmentId') ?? null;
|
if (environmentId == null || environmentId === '__default__') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const setId = useCallback((id: string) => {
|
return environmentId;
|
||||||
searchParams.set('environmentId', id)
|
|
||||||
setSearchParams(searchParams);
|
|
||||||
}, [searchParams, setSearchParams])
|
|
||||||
|
|
||||||
return [id, setId];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
|
import { useActiveRequestId } from './useActiveRequestId';
|
||||||
|
import type { Environment } from '../lib/models';
|
||||||
|
|
||||||
export type RouteParamsWorkspace = {
|
export type RouteParamsWorkspace = {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
|
environmentId: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RouteParamsRequest = RouteParamsWorkspace & {
|
export type RouteParamsRequest = RouteParamsWorkspace & {
|
||||||
@@ -13,23 +17,48 @@ export const routePaths = {
|
|||||||
workspaces() {
|
workspaces() {
|
||||||
return '/workspaces';
|
return '/workspaces';
|
||||||
},
|
},
|
||||||
workspace({ workspaceId } = { workspaceId: ':workspaceId' } as RouteParamsWorkspace) {
|
workspace(
|
||||||
return `/workspaces/${workspaceId}`;
|
{ workspaceId, environmentId } = {
|
||||||
|
workspaceId: ':workspaceId',
|
||||||
|
environmentId: ':environmentId',
|
||||||
|
} as RouteParamsWorkspace,
|
||||||
|
) {
|
||||||
|
return `/workspaces/${workspaceId}/environments/${environmentId ?? '__default__'}`;
|
||||||
},
|
},
|
||||||
request(
|
request(
|
||||||
{ workspaceId, requestId } = {
|
{ workspaceId, environmentId, requestId } = {
|
||||||
workspaceId: ':workspaceId',
|
workspaceId: ':workspaceId',
|
||||||
|
environmentId: ':environmentId',
|
||||||
requestId: ':requestId',
|
requestId: ':requestId',
|
||||||
} as RouteParamsRequest,
|
} as RouteParamsRequest,
|
||||||
) {
|
) {
|
||||||
return `${this.workspace({ workspaceId })}/requests/${requestId}`;
|
return `${this.workspace({ workspaceId, environmentId })}/requests/${requestId}`;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useAppRoutes() {
|
export function useAppRoutes() {
|
||||||
|
const workspaceId = useActiveWorkspaceId();
|
||||||
|
const requestId = useActiveRequestId();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
return useMemo(
|
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<T extends keyof typeof routePaths>(
|
navigate<T extends keyof typeof routePaths>(
|
||||||
path: T,
|
path: T,
|
||||||
...params: Parameters<(typeof routePaths)[T]>
|
...params: Parameters<(typeof routePaths)[T]>
|
||||||
@@ -42,6 +71,6 @@ export function useAppRoutes() {
|
|||||||
},
|
},
|
||||||
paths: routePaths,
|
paths: routePaths,
|
||||||
}),
|
}),
|
||||||
[navigate],
|
[navigate, requestId, workspaceId],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,21 @@ import type { Environment } from '../lib/models';
|
|||||||
import { environmentsQueryKey } from './useEnvironments';
|
import { environmentsQueryKey } from './useEnvironments';
|
||||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
||||||
|
import { useAppRoutes } from './useAppRoutes';
|
||||||
|
|
||||||
export function useCreateEnvironment() {
|
export function useCreateEnvironment() {
|
||||||
|
const environmentId = useActiveEnvironmentId();
|
||||||
const workspaceId = useActiveWorkspaceId();
|
const workspaceId = useActiveWorkspaceId();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [, setActiveEnvironmentId ] = useActiveEnvironmentId();
|
const routes = useAppRoutes();
|
||||||
|
|
||||||
return useMutation<Environment, unknown, Pick<Environment, 'name'>>({
|
return useMutation<Environment, unknown, Pick<Environment, 'name'>>({
|
||||||
mutationFn: (patch) => {
|
mutationFn: (patch) => {
|
||||||
return invoke('create_environment', { ...patch, workspaceId });
|
return invoke('create_environment', { ...patch, workspaceId });
|
||||||
},
|
},
|
||||||
onSuccess: async (environment) => {
|
onSuccess: async (environment) => {
|
||||||
if (workspaceId == null) return;
|
if (workspaceId == null) return;
|
||||||
setActiveEnvironmentId(environment.id);
|
routes.navigate('workspace', { workspaceId, environmentId });
|
||||||
queryClient.setQueryData<Environment[]>(
|
queryClient.setQueryData<Environment[]>(
|
||||||
environmentsQueryKey({ workspaceId }),
|
environmentsQueryKey({ workspaceId }),
|
||||||
(environments) => [...(environments ?? []), environment],
|
(environments) => [...(environments ?? []), environment],
|
||||||
|
|||||||
Reference in New Issue
Block a user