mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 09:18:30 +02:00
More layout fiddling and error page
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
use tauri::{Runtime, Window};
|
use tauri::{Runtime, Window};
|
||||||
|
|
||||||
const TRAFFIC_LIGHT_OFFSET_X: f64 = 15.0;
|
const TRAFFIC_LIGHT_OFFSET_X: f64 = 15.0;
|
||||||
const TRAFFIC_LIGHT_OFFSET_Y: f64 = 26.0;
|
const TRAFFIC_LIGHT_OFFSET_Y: f64 = 20.0;
|
||||||
|
|
||||||
pub trait WindowExt {
|
pub trait WindowExt {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { Button } from './components/Button';
|
import { Button } from './components/Button';
|
||||||
import { Divider } from './components/Divider';
|
import { Divider } from './components/Divider';
|
||||||
import { Grid } from './components/Grid';
|
import { Grid } from './components/Grid';
|
||||||
|
import { IconButton } from './components/IconButton';
|
||||||
import { RequestPane } from './components/RequestPane';
|
import { RequestPane } from './components/RequestPane';
|
||||||
import { ResponsePane } from './components/ResponsePane';
|
import { ResponsePane } from './components/ResponsePane';
|
||||||
import { Sidebar } from './components/Sidebar';
|
import { Sidebar } from './components/Sidebar';
|
||||||
import { HStack } from './components/Stacks';
|
import { HStack } from './components/Stacks';
|
||||||
|
import { WindowDragRegion } from './components/WindowDragRegion';
|
||||||
import {
|
import {
|
||||||
useDeleteRequest,
|
useDeleteRequest,
|
||||||
useRequests,
|
useRequests,
|
||||||
@@ -24,6 +26,7 @@ function App() {
|
|||||||
const p = useParams<Params>();
|
const p = useParams<Params>();
|
||||||
const workspaceId = p.workspaceId ?? '';
|
const workspaceId = p.workspaceId ?? '';
|
||||||
const { data: requests } = useRequests(workspaceId);
|
const { data: requests } = useRequests(workspaceId);
|
||||||
|
const navigate = useNavigate();
|
||||||
const request = requests?.find((r) => r.id === p.requestId);
|
const request = requests?.find((r) => r.id === p.requestId);
|
||||||
|
|
||||||
const [screenWidth, setScreenWidth] = useState(window.innerWidth);
|
const [screenWidth, setScreenWidth] = useState(window.innerWidth);
|
||||||
@@ -39,12 +42,20 @@ function App() {
|
|||||||
<div className="p-2 h-full">
|
<div className="p-2 h-full">
|
||||||
<div className="grid grid-rows-[auto_1fr] rounded-md h-full overflow-hidden">
|
<div className="grid grid-rows-[auto_1fr] rounded-md h-full overflow-hidden">
|
||||||
<HStack
|
<HStack
|
||||||
data-tauri-drag-region
|
as={WindowDragRegion}
|
||||||
className="h-10 px-3 bg-gray-50"
|
className="pl-1 pr-3 bg-gray-50 text-sm"
|
||||||
justify="center"
|
justify="center"
|
||||||
items="center"
|
items="center"
|
||||||
>
|
>
|
||||||
{request.name}
|
<div className="mr-auto">
|
||||||
|
<IconButton
|
||||||
|
size="xs"
|
||||||
|
icon="x"
|
||||||
|
onClick={() => navigate(`/workspaces/${workspaceId}`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>{request.name}</div>
|
||||||
|
<div className="ml-auto"></div>
|
||||||
</HStack>
|
</HStack>
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Icon } from './Icon';
|
|||||||
|
|
||||||
const colorStyles = {
|
const colorStyles = {
|
||||||
default: 'hover:bg-gray-500/10 text-gray-600',
|
default: 'hover:bg-gray-500/10 text-gray-600',
|
||||||
gray: 'bg-gray-50 text-gray-800 hover:bg-gray-500/10',
|
gray: 'text-gray-800 bg-gray-50 hover:bg-gray-500/20',
|
||||||
primary: 'bg-blue-400',
|
primary: 'bg-blue-400',
|
||||||
secondary: 'bg-violet-400',
|
secondary: 'bg-violet-400',
|
||||||
warning: 'bg-orange-400',
|
warning: 'bg-orange-400',
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor {
|
.cm-editor {
|
||||||
@apply w-full block text-[0.85rem];
|
@apply w-full block text-[0.85rem] bg-gray-25;
|
||||||
|
|
||||||
&.cm-focused {
|
&.cm-focused {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-widget {
|
.placeholder-widget {
|
||||||
@apply text-xs text-white/90 bg-blue-400/80 py-[1px] px-1 mx-[1px] rounded cursor-default hover:bg-blue-400 hover:text-white;
|
@apply text-xs text-white/90 bg-blue-400/80 py-[0.5px] px-1 mx-[1px] rounded cursor-default hover:bg-blue-400 hover:text-white;
|
||||||
text-shadow: 0 0 1px rgba(0, 0, 0, 0.9);
|
text-shadow: 0 0 1px rgba(0, 0, 0, 0.9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,8 +70,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor .cm-gutters {
|
.cm-editor .cm-gutters {
|
||||||
/*@apply bg-gray-50 border-r-0 text-gray-200;*/
|
@apply bg-gray-25 border-0 text-gray-200;
|
||||||
@apply bg-transparent border-0 text-gray-200;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor .cm-gutterElement {
|
.cm-editor .cm-gutterElement {
|
||||||
|
|||||||
12
src-web/components/Heading.tsx
Normal file
12
src-web/components/Heading.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import classnames from 'classnames';
|
||||||
|
import type { HTMLAttributes } from 'react';
|
||||||
|
|
||||||
|
type Props = HTMLAttributes<HTMLHeadingElement>;
|
||||||
|
|
||||||
|
export function Heading({ className, children, ...props }: Props) {
|
||||||
|
return (
|
||||||
|
<h1 className={classnames(className, 'text-2xl font-semibold text-gray-900 mb-3')} {...props}>
|
||||||
|
{children}
|
||||||
|
</h1>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -122,13 +122,16 @@ export function ResponsePane({ requestId, className }: Props) {
|
|||||||
))}
|
))}
|
||||||
</HStack>
|
</HStack>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
||||||
{viewMode === 'pretty' && contentForIframe !== null ? (
|
{viewMode === 'pretty' && contentForIframe !== null ? (
|
||||||
<iframe
|
<div className="pl-2">
|
||||||
title="Response preview"
|
<iframe
|
||||||
srcDoc={contentForIframe}
|
title="Response preview"
|
||||||
sandbox="allow-scripts allow-same-origin"
|
srcDoc={contentForIframe}
|
||||||
className="h-full w-full rounded-lg"
|
sandbox="allow-scripts allow-same-origin"
|
||||||
/>
|
className="h-full w-full rounded-lg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
) : response?.body ? (
|
) : response?.body ? (
|
||||||
<Editor
|
<Editor
|
||||||
valueKey={`${contentType}:${response.body}`}
|
valueKey={`${contentType}:${response.body}`}
|
||||||
|
|||||||
24
src-web/components/RouterError.tsx
Normal file
24
src-web/components/RouterError.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Link, useRouteError } from 'react-router-dom';
|
||||||
|
import { Button } from './Button';
|
||||||
|
import { ButtonLink } from './ButtonLink';
|
||||||
|
import { Heading } from './Heading';
|
||||||
|
import { VStack } from './Stacks';
|
||||||
|
|
||||||
|
export function RouterError() {
|
||||||
|
const error = useRouteError();
|
||||||
|
console.log('Router Error', error);
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center h-full">
|
||||||
|
<VStack space={5} className="w-auto h-auto">
|
||||||
|
<Heading>Route Error 🔥</Heading>
|
||||||
|
<pre className="text-sm select-auto cursor-text bg-gray-50 p-3 rounded">
|
||||||
|
{JSON.stringify(error, null, 2)}
|
||||||
|
</pre>
|
||||||
|
<ButtonLink to="/" color="primary">
|
||||||
|
Go Home
|
||||||
|
</ButtonLink>
|
||||||
|
</VStack>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,10 +7,8 @@ import useTheme from '../hooks/useTheme';
|
|||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { Button } from './Button';
|
import { Button } from './Button';
|
||||||
import { Dialog } from './Dialog';
|
import { Dialog } from './Dialog';
|
||||||
import { DropdownMenuRadio } from './Dropdown';
|
|
||||||
import { HeaderEditor } from './HeaderEditor';
|
import { HeaderEditor } from './HeaderEditor';
|
||||||
import { IconButton } from './IconButton';
|
import { IconButton } from './IconButton';
|
||||||
import { Input } from './Input';
|
|
||||||
import { HStack, VStack } from './Stacks';
|
import { HStack, VStack } from './Stacks';
|
||||||
import { WindowDragRegion } from './WindowDragRegion';
|
import { WindowDragRegion } from './WindowDragRegion';
|
||||||
|
|
||||||
@@ -25,8 +23,8 @@ export function Sidebar({ className, activeRequestId, workspaceId, requests, ...
|
|||||||
const { toggleTheme } = useTheme();
|
const { toggleTheme } = useTheme();
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
return (
|
return (
|
||||||
<div className={classnames(className, 'w-52 bg-gray-50 h-full')} {...props}>
|
<div className={classnames(className, 'w-52 bg-gray-50 h-full px-2')} {...props}>
|
||||||
<HStack as={WindowDragRegion} items="center" className="pr-1" justify="end">
|
<HStack as={WindowDragRegion} items="center" className="py-2" justify="end">
|
||||||
<Dialog wide open={open} onOpenChange={setOpen} title="Edit Headers">
|
<Dialog wide open={open} onOpenChange={setOpen} title="Edit Headers">
|
||||||
<HeaderEditor />
|
<HeaderEditor />
|
||||||
<Button className="ml-auto mt-5" color="primary" onClick={() => setOpen(false)}>
|
<Button className="ml-auto mt-5" color="primary" onClick={() => setOpen(false)}>
|
||||||
@@ -60,12 +58,12 @@ export function Sidebar({ className, activeRequestId, workspaceId, requests, ...
|
|||||||
|
|
||||||
function SidebarItem({ request, active }: { request: HttpRequest; active: boolean }) {
|
function SidebarItem({ request, active }: { request: HttpRequest; active: boolean }) {
|
||||||
return (
|
return (
|
||||||
<li key={request.id} className="mx-3">
|
<li key={request.id}>
|
||||||
<Button
|
<Button
|
||||||
as={Link}
|
as={Link}
|
||||||
to={`/workspaces/${request.workspaceId}/requests/${request.id}`}
|
to={`/workspaces/${request.workspaceId}/requests/${request.id}`}
|
||||||
className={classnames('w-full', active && 'bg-gray-500/[0.1]')}
|
className={classnames('w-full', active && 'bg-gray-500/[0.1] text-gray-900')}
|
||||||
size="xs"
|
size="sm"
|
||||||
justify="start"
|
justify="start"
|
||||||
>
|
>
|
||||||
{request.name}
|
{request.name}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ const spaceClassesX = {
|
|||||||
2: 'pr-2',
|
2: 'pr-2',
|
||||||
3: 'pr-3',
|
3: 'pr-3',
|
||||||
4: 'pr-4',
|
4: 'pr-4',
|
||||||
|
5: 'pr-5',
|
||||||
|
6: 'pr-6',
|
||||||
};
|
};
|
||||||
|
|
||||||
const spaceClassesY = {
|
const spaceClassesY = {
|
||||||
@@ -16,6 +18,8 @@ const spaceClassesY = {
|
|||||||
2: 'pt-2',
|
2: 'pt-2',
|
||||||
3: 'pt-3',
|
3: 'pt-3',
|
||||||
4: 'pt-4',
|
4: 'pt-4',
|
||||||
|
5: 'pt-5',
|
||||||
|
6: 'pt-6',
|
||||||
};
|
};
|
||||||
|
|
||||||
interface HStackProps extends BaseStackProps {
|
interface HStackProps extends BaseStackProps {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ type Props = HTMLAttributes<HTMLDivElement>;
|
|||||||
export function WindowDragRegion({ className, ...props }: Props) {
|
export function WindowDragRegion({ className, ...props }: Props) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classnames(className, 'w-full h-14 flex-shrink-0')}
|
data-tauri-drag-region
|
||||||
data-tauri-drag-region=""
|
className={classnames(className, 'w-full h-8 flex-shrink-0 box-content')}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import React from 'react';
|
|
||||||
import init, { greet } from 'hello';
|
|
||||||
import ReactDOM from 'react-dom/client';
|
|
||||||
import App from './App';
|
|
||||||
import { HelmetProvider } from 'react-helmet-async';
|
|
||||||
import { MotionConfig } from 'framer-motion';
|
|
||||||
import { listen } from '@tauri-apps/api/event';
|
|
||||||
import { responsesQueryKey } from './hooks/useResponses';
|
|
||||||
import { setTheme } from './lib/theme';
|
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
import { listen } from '@tauri-apps/api/event';
|
||||||
|
import { MotionConfig } from 'framer-motion';
|
||||||
|
import init, { greet } from 'hello';
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import { HelmetProvider } from 'react-helmet-async';
|
||||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||||
|
import App from './App';
|
||||||
import { Layout } from './components/Layout';
|
import { Layout } from './components/Layout';
|
||||||
import { Workspaces } from './pages/Workspaces';
|
import { RouterError } from './components/RouterError';
|
||||||
import './main.css';
|
import { requestsQueryKey } from './hooks/useRequest';
|
||||||
|
import { responsesQueryKey } from './hooks/useResponses';
|
||||||
import type { HttpRequest, HttpResponse } from './lib/models';
|
import type { HttpRequest, HttpResponse } from './lib/models';
|
||||||
import { convertDates } from './lib/models';
|
import { convertDates } from './lib/models';
|
||||||
import { requestsQueryKey } from './hooks/useRequest';
|
import { setTheme } from './lib/theme';
|
||||||
|
import './main.css';
|
||||||
|
import { Workspaces } from './pages/Workspaces';
|
||||||
|
|
||||||
setTheme();
|
setTheme();
|
||||||
|
|
||||||
@@ -78,6 +79,7 @@ const router = createBrowserRouter([
|
|||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
element: <Layout />,
|
element: <Layout />,
|
||||||
|
errorElement: <RouterError />,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
|
import { Heading } from '../components/Heading';
|
||||||
|
import { VStack } from '../components/Stacks';
|
||||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||||
import { ButtonLink } from '../components/ButtonLink';
|
import { ButtonLink } from '../components/ButtonLink';
|
||||||
|
|
||||||
export function Workspaces() {
|
export function Workspaces() {
|
||||||
const workspaces = useWorkspaces();
|
const workspaces = useWorkspaces();
|
||||||
return (
|
return (
|
||||||
<ul className="p-12">
|
<VStack as="ul" className="p-12">
|
||||||
|
<Heading>Workspaces</Heading>
|
||||||
{workspaces.data?.map((w) => (
|
{workspaces.data?.map((w) => (
|
||||||
<ButtonLink key={w.id} to={`/workspaces/${w.id}`}>
|
<ButtonLink key={w.id} color="gray" to={`/workspaces/${w.id}`}>
|
||||||
{w.name}
|
{w.name}
|
||||||
</ButtonLink>
|
</ButtonLink>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</VStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user