mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-18 23:43:55 +01:00
Environments in URL and better rendering
This commit is contained in:
@@ -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: <Workspaces />,
|
||||
},
|
||||
{
|
||||
path: routePaths.workspace({ workspaceId: ':workspaceId' }),
|
||||
path: routePaths.workspace({
|
||||
workspaceId: ':workspaceId',
|
||||
environmentId: ':environmentId',
|
||||
}),
|
||||
element: <WorkspaceOrRedirect />,
|
||||
},
|
||||
{
|
||||
path: routePaths.request({
|
||||
workspaceId: ':workspaceId',
|
||||
environmentId: ':environmentId',
|
||||
requestId: ':requestId',
|
||||
}),
|
||||
element: <Workspace />,
|
||||
@@ -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 <Workspace />;
|
||||
}
|
||||
|
||||
return (
|
||||
<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';
|
||||
|
||||
interface Props {
|
||||
@@ -9,7 +9,7 @@ export const DropMarker = memo(
|
||||
function DropMarker({ className }: Props) {
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'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 { Button } from './core/Button';
|
||||
import type { DropdownItem } from './core/Dropdown';
|
||||
@@ -12,6 +12,7 @@ import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
|
||||
import { usePrompt } from '../hooks/usePrompt';
|
||||
import { useDialog } from './DialogContext';
|
||||
import { EnvironmentEditDialog } from './EnvironmentEditDialog';
|
||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
@@ -21,11 +22,12 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
||||
className,
|
||||
}: Props) {
|
||||
const environments = useEnvironments();
|
||||
const [activeEnvironment, setActiveEnvironment] = useActiveEnvironment();
|
||||
const activeEnvironment = useActiveEnvironment();
|
||||
const updateEnvironment = useUpdateEnvironment(activeEnvironment?.id ?? null);
|
||||
const createEnvironment = useCreateEnvironment();
|
||||
const prompt = usePrompt();
|
||||
const dialog = useDialog();
|
||||
const routes = useAppRoutes();
|
||||
|
||||
const items: DropdownItem[] = useMemo(() => {
|
||||
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 (
|
||||
<Dropdown items={items}>
|
||||
<Button
|
||||
size="sm"
|
||||
className={classnames(className, 'text-gray-800 !px-2 truncate')}
|
||||
className={classNames(className, 'text-gray-800 !px-2 truncate')}
|
||||
forDropdown
|
||||
>
|
||||
{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 { Button } from './core/Button';
|
||||
import { Editor } from './core/Editor';
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||
|
||||
export const EnvironmentEditDialog = function() {
|
||||
const routes = useAppRoutes();
|
||||
const prompt = usePrompt();
|
||||
const environments = useEnvironments();
|
||||
const createEnvironment = useCreateEnvironment();
|
||||
const [activeEnvironment, setActiveEnvironment] = useActiveEnvironment();
|
||||
const activeEnvironment = useActiveEnvironment();
|
||||
|
||||
return (
|
||||
<div className="h-full grid gap-3 grid-cols-[auto_minmax(0,1fr)]">
|
||||
@@ -20,14 +23,14 @@ export const EnvironmentEditDialog = function() {
|
||||
{environments.map((e) => (
|
||||
<Button
|
||||
size="sm"
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
'w-full',
|
||||
activeEnvironment?.id === e.id && 'bg-gray-100 text-gray-1000',
|
||||
)}
|
||||
justify="start"
|
||||
key={e.id}
|
||||
onClick={() => {
|
||||
setActiveEnvironment(e);
|
||||
routes.setEnvironment(e);
|
||||
}}
|
||||
>
|
||||
{e.name}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { motion } from 'framer-motion';
|
||||
import type { ReactNode } from 'react';
|
||||
@@ -26,7 +26,7 @@ export function Overlay({ zIndex = 30, open, onClose, portalName, children }: Pr
|
||||
{open && (
|
||||
<FocusTrap>
|
||||
<motion.div
|
||||
className={classnames('fixed inset-0', zIndexes[zIndex])}
|
||||
className={classNames('fixed inset-0', zIndexes[zIndex])}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { appWindow } from '@tauri-apps/api/window';
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo, useCallback, useMemo, useState } from 'react';
|
||||
import { createGlobalState } from 'react-use';
|
||||
@@ -152,7 +152,7 @@ export const RequestPane = memo(function RequestPane({ style, fullHeight, classN
|
||||
return (
|
||||
<div
|
||||
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 && (
|
||||
<>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import useResizeObserver from '@react-hook/resize-observer';
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties, MouseEvent as ReactMouseEvent } from 'react';
|
||||
import React, { memo, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
@@ -120,7 +120,7 @@ export const RequestResponse = memo(function RequestResponse({ style }: Props) {
|
||||
<ResizeHandle
|
||||
style={drag}
|
||||
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}
|
||||
onReset={handleReset}
|
||||
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 React from 'react';
|
||||
|
||||
@@ -28,7 +28,7 @@ export function ResizeHandle({
|
||||
aria-hidden
|
||||
draggable
|
||||
style={style}
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'group z-10 flex',
|
||||
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 */}
|
||||
{isResizing && (
|
||||
<div
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
'fixed -left-20 -right-20 -top-20 -bottom-20',
|
||||
vertical && 'cursor-row-resize',
|
||||
!vertical && 'cursor-col-resize',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import type { HttpResponse } from '../lib/models';
|
||||
import { HStack } from './core/Stacks';
|
||||
|
||||
@@ -14,7 +14,7 @@ export function ResponseHeaders({ headers }: Props) {
|
||||
<HStack
|
||||
space={3}
|
||||
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>
|
||||
<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 { useCallback, memo, useEffect, useMemo, useState } from 'react';
|
||||
import { createGlobalState } from 'react-use';
|
||||
@@ -84,7 +84,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
return (
|
||||
<div
|
||||
style={style}
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'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',
|
||||
@@ -96,7 +96,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
<>
|
||||
<HStack
|
||||
alignItems="center"
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
'text-gray-700 text-sm w-full flex-shrink-0',
|
||||
// Remove a bit of space because the tabs have lots too
|
||||
'-mb-1.5',
|
||||
|
||||
@@ -7,13 +7,14 @@ import { VStack } from './core/Stacks';
|
||||
|
||||
export default function RouteError() {
|
||||
const error = useRouteError();
|
||||
console.log("Error", error);
|
||||
const stringified = JSON.stringify(error);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const message = (error as any).message ?? stringified;
|
||||
const routes = useAppRoutes();
|
||||
return (
|
||||
<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>
|
||||
<FormattedError>{message}</FormattedError>
|
||||
<VStack space={2}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import React, { forwardRef, Fragment, memo, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import type { XYCoord } from 'react-dnd';
|
||||
@@ -19,6 +19,7 @@ import { Icon } from './core/Icon';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { StatusTag } from './core/StatusTag';
|
||||
import { DropMarker } from './DropMarker';
|
||||
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@@ -32,6 +33,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
||||
const { hidden } = useSidebarHidden();
|
||||
const sidebarRef = useRef<HTMLDivElement>(null);
|
||||
const activeRequestId = useActiveRequestId();
|
||||
const activeEnvironmentId = useActiveEnvironmentId();
|
||||
const unorderedRequests = useRequests();
|
||||
const deleteAnyRequest = useDeleteAnyRequest();
|
||||
const routes = useAppRoutes();
|
||||
@@ -58,11 +60,15 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
||||
const index = requests.findIndex((r) => r.id === requestId);
|
||||
const request = requests[index];
|
||||
if (!request) return;
|
||||
routes.navigate('request', { requestId, workspaceId: request.workspaceId });
|
||||
routes.navigate('request', {
|
||||
requestId,
|
||||
workspaceId: request.workspaceId,
|
||||
environmentId: activeEnvironmentId,
|
||||
});
|
||||
setSelectedIndex(index);
|
||||
focusActiveRequest(index);
|
||||
},
|
||||
[focusActiveRequest, requests, routes],
|
||||
[focusActiveRequest, requests, routes, activeEnvironmentId],
|
||||
);
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
@@ -143,7 +149,7 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
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
|
||||
as="ul"
|
||||
@@ -299,7 +305,7 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
||||
}, [onSelect, requestId]);
|
||||
|
||||
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
|
||||
// tabIndex={-1} // Will prevent drag-n-drop
|
||||
onClick={handleSelect}
|
||||
@@ -307,7 +313,7 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
||||
onDoubleClick={handleStartEditing}
|
||||
data-active={isActive}
|
||||
data-selected={selected}
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
'w-full flex items-center text-sm h-xs px-2 rounded-md transition-colors',
|
||||
editing && 'ring-1 focus-within:ring-focus',
|
||||
isActive && 'bg-highlight text-gray-800',
|
||||
@@ -324,7 +330,7 @@ const _SidebarItem = forwardRef(function SidebarItem(
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
) : (
|
||||
<span className={classnames('truncate', !requestName && 'text-gray-400 italic')}>
|
||||
<span className={classNames('truncate', !requestName && 'text-gray-400 italic')}>
|
||||
{requestName || 'New Request'}
|
||||
</span>
|
||||
)}
|
||||
@@ -396,7 +402,7 @@ const DraggableSidebarItem = memo(function DraggableSidebarItem({
|
||||
<SidebarItem
|
||||
ref={ref}
|
||||
draggable
|
||||
className={classnames(isDragging && 'opacity-20')}
|
||||
className={classNames(isDragging && 'opacity-20')}
|
||||
requestName={requestName}
|
||||
requestId={requestId}
|
||||
{...props}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import type { EditorView } from 'codemirror';
|
||||
import type { FormEvent } from 'react';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
@@ -44,7 +44,7 @@ export const UrlBar = memo(function UrlBar({ id: requestId, url, method, classNa
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={classnames('url-bar', className)}>
|
||||
<form onSubmit={handleSubmit} className={classNames('url-bar', className)}>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
size="sm"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import { motion } from 'framer-motion';
|
||||
import type {
|
||||
CSSProperties,
|
||||
@@ -113,7 +113,7 @@ export default function Workspace() {
|
||||
return (
|
||||
<div
|
||||
style={styles}
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
'grid w-full h-full',
|
||||
// Animate sidebar width changes but only when not resizing
|
||||
// because it's too slow to animate on mouse move
|
||||
@@ -125,7 +125,7 @@ export default function Workspace() {
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
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]',
|
||||
'grid grid-rows-[auto_1fr]',
|
||||
)}
|
||||
@@ -140,7 +140,7 @@ export default function Workspace() {
|
||||
</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" />
|
||||
</div>
|
||||
<ResizeHandle
|
||||
@@ -173,7 +173,7 @@ function HeaderSize({ className, ...props }: HeaderSizeProps) {
|
||||
const platform = useOsInfo();
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'h-md pt-[1px] flex items-center w-full pr-3 pl-20 border-b',
|
||||
platform?.osType === 'Darwin' && 'pl-20',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||
@@ -15,6 +15,7 @@ import { Icon } from './core/Icon';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { useDialog } from './DialogContext';
|
||||
import { useActiveEnvironmentId } from '../hooks/useActiveEnvironmentId';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
@@ -24,6 +25,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
const workspaces = useWorkspaces();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeWorkspaceId = activeWorkspace?.id ?? null;
|
||||
const environmentId = useActiveEnvironmentId();
|
||||
const createWorkspace = useCreateWorkspace({ navigateAfter: true });
|
||||
const updateWorkspace = useUpdateWorkspace(activeWorkspaceId);
|
||||
const deleteWorkspace = useDeleteWorkspace(activeWorkspace);
|
||||
@@ -53,7 +55,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
color="gray"
|
||||
onClick={() => {
|
||||
hide();
|
||||
routes.navigate('workspace', { workspaceId: w.id });
|
||||
routes.navigate('workspace', { workspaceId: w.id, environmentId });
|
||||
}}
|
||||
>
|
||||
This Window
|
||||
@@ -66,7 +68,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
onClick={async () => {
|
||||
hide();
|
||||
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}>
|
||||
<Button
|
||||
size="sm"
|
||||
className={classnames(className, 'text-gray-800 !px-2 truncate')}
|
||||
className={classNames(className, 'text-gray-800 !px-2 truncate')}
|
||||
forDropdown
|
||||
>
|
||||
{activeWorkspace?.name}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import { memo } from 'react';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { IconButton } from './core/IconButton';
|
||||
@@ -20,7 +20,7 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
|
||||
<HStack
|
||||
justifyContent="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">
|
||||
<SidebarActions />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
interface Props {
|
||||
@@ -9,7 +9,7 @@ export function Banner({ children, className }: Props) {
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'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 { forwardRef, memo, useMemo } from 'react';
|
||||
import { Icon } from './Icon';
|
||||
@@ -45,7 +45,7 @@ const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
||||
) {
|
||||
const classes = useMemo(
|
||||
() =>
|
||||
classnames(
|
||||
classNames(
|
||||
className,
|
||||
'flex-shrink-0 outline-none whitespace-nowrap',
|
||||
'focus-visible-or-class:ring',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import { useCallback } from 'react';
|
||||
import { Icon } from './Icon';
|
||||
|
||||
@@ -20,7 +20,7 @@ export function Checkbox({ checked, onChange, className, disabled }: Props) {
|
||||
aria-checked={checked ? 'true' : 'false'}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'flex-shrink-0 w-4 h-4 border border-gray-200 rounded',
|
||||
'focus:border-focus',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
count: number;
|
||||
@@ -10,7 +10,7 @@ export function CountBadge({ count, className }: Props) {
|
||||
return (
|
||||
<div
|
||||
aria-hidden
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'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 type { ReactNode } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
@@ -51,7 +51,7 @@ export function Dialog({
|
||||
<motion.div
|
||||
initial={{ top: 5, scale: 0.97 }}
|
||||
animate={{ top: 0, scale: 1 }}
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'gap-2 grid grid-rows-[auto_minmax(0,1fr)]',
|
||||
'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 { motion } from 'framer-motion';
|
||||
import type { CSSProperties, HTMLAttributes, MouseEvent, ReactElement, ReactNode } from 'react';
|
||||
@@ -278,7 +278,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
||||
dir="ltr"
|
||||
ref={containerRef}
|
||||
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
|
||||
aria-hidden
|
||||
@@ -290,7 +290,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
||||
space={0.5}
|
||||
ref={initMenu}
|
||||
style={menuStyles}
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'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',
|
||||
@@ -356,7 +356,7 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
||||
onFocus={handleFocus}
|
||||
onClick={handleClick}
|
||||
justify="start"
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'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',
|
||||
@@ -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>}
|
||||
<div
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
// Add padding on right when no right slot, for some visual balance
|
||||
!item.rightSlot && 'pr-4',
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defaultKeymap } from '@codemirror/commands';
|
||||
import { Compartment, EditorState, Transaction } from '@codemirror/state';
|
||||
import type { ViewUpdate } 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 type { MutableRefObject, ReactNode } 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 = (
|
||||
<div
|
||||
ref={initEditorRef}
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'cm-wrapper text-base bg-gray-50',
|
||||
type === 'password' && 'cm-obscure-text',
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
children: string;
|
||||
}
|
||||
|
||||
export function FormattedError({ children }: Props) {
|
||||
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}
|
||||
</pre>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
export function Heading({ className, children, ...props }: HTMLAttributes<HTMLHeadingElement>) {
|
||||
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}
|
||||
</h1>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
modifier: 'Meta' | 'Control' | 'Shift';
|
||||
@@ -13,7 +13,7 @@ const keys: Record<Props['modifier'], string> = {
|
||||
|
||||
export function HotKey({ modifier, keyName }: Props) {
|
||||
return (
|
||||
<span className={classnames('text-sm text-gray-600')}>
|
||||
<span className={classNames('text-sm text-gray-600')}>
|
||||
{keys[modifier]}
|
||||
{keyName}
|
||||
</span>
|
||||
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
TriangleRightIcon,
|
||||
UpdateIcon,
|
||||
} from '@radix-ui/react-icons';
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { memo } from 'react';
|
||||
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;
|
||||
return (
|
||||
<Component
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'text-inherit',
|
||||
size === 'md' && 'h-4 w-4',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { forwardRef, useCallback } from 'react';
|
||||
import { useTimedBoolean } from '../../hooks/useTimedBoolean';
|
||||
@@ -45,7 +45,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
|
||||
disabled={icon === 'empty'}
|
||||
tabIndex={tabIndex ?? icon === 'empty' ? -1 : undefined}
|
||||
onClick={handleClick}
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'flex-shrink-0 text-gray-700 hover:text-gray-1000',
|
||||
'!px-0',
|
||||
@@ -60,7 +60,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(function IconButt
|
||||
size={iconSize}
|
||||
icon={confirmed ? 'check' : icon}
|
||||
spin={spin}
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
iconClassName,
|
||||
props.disabled && 'opacity-70',
|
||||
confirmed && 'text-green-600',
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
export function InlineCode({ className, ...props }: HTMLAttributes<HTMLSpanElement>) {
|
||||
return (
|
||||
<code
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'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 { HTMLAttributes, ReactNode } from 'react';
|
||||
import { forwardRef, useCallback, useMemo, useState } from 'react';
|
||||
@@ -68,7 +68,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
||||
}, [onBlur]);
|
||||
|
||||
const id = `input-${name}`;
|
||||
const inputClassName = classnames(
|
||||
const inputClassName = classNames(
|
||||
className,
|
||||
'!bg-transparent min-w-0 h-full w-full focus:outline-none placeholder:text-placeholder',
|
||||
// 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">
|
||||
<label
|
||||
htmlFor={id}
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
labelClassName,
|
||||
'font-semibold text-xs uppercase text-gray-700',
|
||||
hideLabel && 'sr-only',
|
||||
@@ -104,7 +104,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
|
||||
</label>
|
||||
<HStack
|
||||
alignItems="center"
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
containerClassName,
|
||||
'relative w-full rounded-md text-gray-900',
|
||||
'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 type { XYCoord } from 'react-dnd';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
@@ -134,7 +134,7 @@ export const PairEditor = memo(function PairEditor({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'@container',
|
||||
'pb-2 grid overflow-auto max-h-full',
|
||||
@@ -264,7 +264,7 @@ const FormRow = memo(function FormRow({
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'group grid grid-cols-[auto_auto_minmax(0,1fr)_auto]',
|
||||
'grid-rows-1 items-center',
|
||||
@@ -273,7 +273,7 @@ const FormRow = memo(function FormRow({
|
||||
>
|
||||
{!isLast ? (
|
||||
<div
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
'py-2 h-7 w-3 flex items-center',
|
||||
'justify-center opacity-0 hover:opacity-100',
|
||||
)}
|
||||
@@ -286,11 +286,11 @@ const FormRow = memo(function FormRow({
|
||||
<Checkbox
|
||||
disabled={isLast}
|
||||
checked={isLast ? false : !!pairContainer.pair.enabled}
|
||||
className={classnames('mr-2', isLast && '!opacity-disabled')}
|
||||
className={classNames('mr-2', isLast && '!opacity-disabled')}
|
||||
onChange={handleChangeEnabled}
|
||||
/>
|
||||
<div
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
'grid items-center',
|
||||
'@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',
|
||||
@@ -303,7 +303,7 @@ const FormRow = memo(function FormRow({
|
||||
validate={nameValidate}
|
||||
useTemplating
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
containerClassName={classnames(isLast && 'border-dashed')}
|
||||
containerClassName={classNames(isLast && 'border-dashed')}
|
||||
defaultValue={pairContainer.pair.name}
|
||||
label="Name"
|
||||
name="name"
|
||||
@@ -315,7 +315,7 @@ const FormRow = memo(function FormRow({
|
||||
<Input
|
||||
hideLabel
|
||||
size="sm"
|
||||
containerClassName={classnames(isLast && 'border-dashed')}
|
||||
containerClassName={classNames(isLast && 'border-dashed')}
|
||||
validate={valueValidate}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
defaultValue={pairContainer.pair.value}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
orientation?: 'horizontal' | 'vertical';
|
||||
@@ -14,10 +14,10 @@ export function Separator({
|
||||
label,
|
||||
}: Props) {
|
||||
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>}
|
||||
<div
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
variant === 'primary' && 'bg-highlight',
|
||||
variant === 'secondary' && 'bg-highlightSecondary',
|
||||
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 { forwardRef } from 'react';
|
||||
|
||||
@@ -25,7 +25,7 @@ export const HStack = forwardRef(function HStack(
|
||||
return (
|
||||
<BaseStack
|
||||
ref={ref}
|
||||
className={classnames(className, 'flex-row', space != null && gapClasses[space])}
|
||||
className={classNames(className, 'flex-row', space != null && gapClasses[space])}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@@ -45,7 +45,7 @@ export const VStack = forwardRef(function VStack(
|
||||
return (
|
||||
<BaseStack
|
||||
ref={ref}
|
||||
className={classnames(className, 'flex-col', space != null && gapClasses[space])}
|
||||
className={classNames(className, 'flex-col', space != null && gapClasses[space])}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
@@ -69,7 +69,7 @@ const BaseStack = forwardRef(function BaseStack(
|
||||
return (
|
||||
<Component
|
||||
ref={ref}
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'flex',
|
||||
alignItems === 'center' && 'items-center',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import type { HttpResponse } from '../../lib/models';
|
||||
|
||||
interface Props {
|
||||
@@ -12,7 +12,7 @@ export function StatusTag({ response, className, showReason }: Props) {
|
||||
const label = error ? 'ERR' : status;
|
||||
return (
|
||||
<span
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
className,
|
||||
'font-mono',
|
||||
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 { memo, useCallback, useEffect, useRef } from 'react';
|
||||
import { Button } from '../Button';
|
||||
@@ -67,11 +67,11 @@ export function Tabs({
|
||||
return (
|
||||
<div
|
||||
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
|
||||
aria-label={label}
|
||||
className={classnames(
|
||||
className={classNames(
|
||||
tabListClassName,
|
||||
'flex items-center overflow-x-auto overflow-y-visible hide-scrollbars mt-1 mb-2',
|
||||
// Give space for button focus states within overflow boundary.
|
||||
@@ -81,7 +81,7 @@ export function Tabs({
|
||||
<HStack space={2} className="flex-shrink-0">
|
||||
{tabs.map((t) => {
|
||||
const isActive = t.value === value;
|
||||
const btnClassName = classnames(
|
||||
const btnClassName = classNames(
|
||||
isActive ? '' : 'text-gray-600 hover:text-gray-800',
|
||||
'!px-2 ml-[1px]',
|
||||
);
|
||||
@@ -108,7 +108,7 @@ export function Tabs({
|
||||
: option?.label ?? 'Unknown'}
|
||||
<Icon
|
||||
icon="triangleDown"
|
||||
className={classnames('-mr-1.5', isActive ? 'opacity-100' : 'opacity-20')}
|
||||
className={classNames('-mr-1.5', isActive ? 'opacity-100' : 'opacity-20')}
|
||||
/>
|
||||
</Button>
|
||||
</RadioDropdown>
|
||||
@@ -149,7 +149,7 @@ export const TabContent = memo(function TabContent({
|
||||
<div
|
||||
tabIndex={-1}
|
||||
data-tab={value}
|
||||
className={classnames(className, 'tab-content', 'hidden w-full h-full')}
|
||||
className={classNames(className, 'tab-content', 'hidden w-full h-full')}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className={classnames(className, 'w-full flex-shrink-0')}
|
||||
className={classNames(className, 'w-full flex-shrink-0')}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<div className="overflow-auto h-full">
|
||||
<table className={classnames(className, 'text-sm')}>
|
||||
<table className={classNames(className, 'text-sm')}>
|
||||
<tbody>
|
||||
{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) => (
|
||||
<td key={j} className="border-r px-1.5">
|
||||
{col}
|
||||
|
||||
@@ -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) {
|
||||
<img
|
||||
src={src}
|
||||
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 { 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;
|
||||
}
|
||||
|
||||
@@ -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<RouteParamsRequest>();
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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<T extends keyof typeof routePaths>(
|
||||
path: T,
|
||||
...params: Parameters<(typeof routePaths)[T]>
|
||||
@@ -42,6 +71,6 @@ export function useAppRoutes() {
|
||||
},
|
||||
paths: routePaths,
|
||||
}),
|
||||
[navigate],
|
||||
[navigate, requestId, workspaceId],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<Environment, unknown, Pick<Environment, 'name'>>({
|
||||
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<Environment[]>(
|
||||
environmentsQueryKey({ workspaceId }),
|
||||
(environments) => [...(environments ?? []), environment],
|
||||
|
||||
Reference in New Issue
Block a user