From 83bb18df0344e8b3f1b40d4cc32bf36e130ff923 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sat, 25 Feb 2023 18:04:14 -0800 Subject: [PATCH] Added react-router --- package-lock.json | 63 ++++++++++++++++++++++++++++++- package.json | 3 +- src-tauri/src/models.rs | 4 +- src-web/App.tsx | 29 ++++++++------ src-web/components/Button.tsx | 38 +++++++++---------- src-web/components/Dropdown.tsx | 21 +++++------ src-web/components/IconButton.tsx | 12 ++---- src-web/components/Sidebar.tsx | 16 +++++--- src-web/hooks/useWorkspaces.ts | 5 +-- src-web/main.tsx | 32 +++++++++++++--- src-web/pages/Layout.tsx | 9 +++++ src-web/pages/Workspaces.tsx | 15 ++++++++ 12 files changed, 180 insertions(+), 67 deletions(-) create mode 100644 src-web/pages/Layout.tsx create mode 100644 src-web/pages/Workspaces.tsx diff --git a/package-lock.json b/package-lock.json index 85c76643..f74e2a5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-helmet-async": "^1.3.0", - "react-query": "^3.39.3" + "react-query": "^3.39.3", + "react-router-dom": "^6.8.1" }, "devDependencies": { "@tauri-apps/cli": "^1.2.2", @@ -1602,6 +1603,14 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@remix-run/router": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz", + "integrity": "sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA==", + "engines": { + "node": ">=14" + } + }, "node_modules/@swc/core": { "version": "1.3.35", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.35.tgz", @@ -5425,6 +5434,36 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, + "node_modules/react-router": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz", + "integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==", + "dependencies": { + "@remix-run/router": "1.3.2" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.1.tgz", + "integrity": "sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ==", + "dependencies": { + "@remix-run/router": "1.3.2", + "react-router": "6.8.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -7571,6 +7610,11 @@ "@babel/runtime": "^7.13.10" } }, + "@remix-run/router": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.3.2.tgz", + "integrity": "sha512-t54ONhl/h75X94SWsHGQ4G/ZrCEguKSRQr7DrjTciJXW0YU1QhlwYeycvK5JgkzlxmvrK7wq1NB/PLtHxoiDcA==" + }, "@swc/core": { "version": "1.3.35", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.35.tgz", @@ -10169,6 +10213,23 @@ } } }, + "react-router": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.8.1.tgz", + "integrity": "sha512-Jgi8BzAJQ8MkPt8ipXnR73rnD7EmZ0HFFb7jdQU24TynGW1Ooqin2KVDN9voSC+7xhqbbCd2cjGUepb6RObnyg==", + "requires": { + "@remix-run/router": "1.3.2" + } + }, + "react-router-dom": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.8.1.tgz", + "integrity": "sha512-67EXNfkQgf34P7+PSb6VlBuaacGhkKn3kpE51+P6zYSG2kiRoumXEL6e27zTa9+PGF2MNXbgIUHTVlleLbIcHQ==", + "requires": { + "@remix-run/router": "1.3.2", + "react-router": "6.8.1" + } + }, "react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", diff --git a/package.json b/package.json index 74a88721..78575072 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-helmet-async": "^1.3.0", - "react-query": "^3.39.3" + "react-query": "^3.39.3", + "react-router-dom": "^6.8.1" }, "devDependencies": { "@tauri-apps/cli": "^1.2.2", diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index 8b17996c..24f7d08d 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -80,7 +80,7 @@ pub async fn create_workspace( description: &str, pool: &Pool, ) -> Result { - let id = generate_id("wrk"); + let id = generate_id("wk"); sqlx::query!( r#" INSERT INTO workspaces (id, name, description) @@ -106,7 +106,7 @@ pub async fn upsert_request( url: &str, pool: &Pool, ) -> Result { - let id = generate_id("wrk"); + let id = generate_id("rq"); sqlx::query!( r#" INSERT INTO requests (id, workspace_id, name, url, method, body, updated_at, headers) diff --git a/src-web/App.tsx b/src-web/App.tsx index 080b784b..d28e12a2 100644 --- a/src-web/App.tsx +++ b/src-web/App.tsx @@ -9,7 +9,8 @@ import { Sidebar } from './components/Sidebar'; import { UrlBar } from './components/UrlBar'; import { Grid } from './components/Grid'; import { motion } from 'framer-motion'; -import { useRequests, useWorkspace, useWorkspaces } from './hooks/useWorkspaces'; +import { useRequests } from './hooks/useWorkspaces'; +import { useParams } from 'react-router-dom'; interface Response { url: string; @@ -21,17 +22,26 @@ interface Response { headers: Record; } +type Params = { + workspaceId: string; + requestId?: string; +}; + function App() { - const { data } = useWorkspace(); - console.log('DATA', data); + const p = useParams(); + const workspaceId = p.workspaceId ?? ''; + const requestId = p.requestId; + const { data: requests } = useRequests(workspaceId); + const request = requests?.find((r) => r.id === requestId); + const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [response, setResponse] = useState(null); - const [url, setUrl] = useState('https://go-server.schier.dev/debug'); - const [body, setBody] = useState(`{\n "foo": "bar"\n}`); + const [url, setUrl] = useState(request?.url ?? ''); + const [body, setBody] = useState(request?.body ?? ''); const [method, setMethod] = useState<{ label: string; value: string }>({ - label: 'GET', - value: 'GET', + label: request?.method ?? 'GET', + value: request?.method ?? 'GET', }); useEffect(() => { @@ -44,9 +54,6 @@ function App() { return () => document.documentElement.removeEventListener('keypress', listener); }, []); - if (!data) return null; - const { requests, workspace } = data; - async function sendRequest() { setLoading(true); setError(null); @@ -73,7 +80,7 @@ function App() { return ( <>
- + diff --git a/src-web/components/Button.tsx b/src-web/components/Button.tsx index c63e5bdc..2829fa59 100644 --- a/src-web/components/Button.tsx +++ b/src-web/components/Button.tsx @@ -1,29 +1,29 @@ -import { ButtonHTMLAttributes, forwardRef } from 'react'; +import { ButtonHTMLAttributes, ComponentPropsWithoutRef, ElementType } from 'react'; import classnames from 'classnames'; import { Icon } from './Icon'; -export type ButtonProps = ButtonHTMLAttributes & { +export interface ButtonProps + extends ButtonHTMLAttributes { color?: 'primary' | 'secondary'; size?: 'xs' | 'sm' | 'md'; justify?: 'start' | 'center'; forDropdown?: boolean; -}; + as?: T; +} -export const Button = forwardRef(function Button( - { - className, - justify = 'center', - children, - size = 'md', - forDropdown, - color, - ...props - }: ButtonProps, - ref, -) { +export function Button({ + className, + as, + justify = 'center', + children, + size = 'md', + forDropdown, + color, + ...props +}: ButtonProps & Omit, keyof ButtonProps>) { + const Component = as || 'button'; return ( - + ); -}); +} diff --git a/src-web/components/Dropdown.tsx b/src-web/components/Dropdown.tsx index c32ba29c..9a7b3d01 100644 --- a/src-web/components/Dropdown.tsx +++ b/src-web/components/Dropdown.tsx @@ -1,16 +1,9 @@ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; import { DropdownMenuRadioGroup } from '@radix-ui/react-dropdown-menu'; import { motion } from 'framer-motion'; -import { - CheckIcon, - ChevronRightIcon, - DotFilledIcon, - HamburgerMenuIcon, -} from '@radix-ui/react-icons'; -import { forwardRef, HTMLAttributes, ReactNode, useState } from 'react'; -import { Button } from './Button'; +import { CheckIcon } from '@radix-ui/react-icons'; +import { forwardRef, HTMLAttributes, ReactNode } from 'react'; import classnames from 'classnames'; -import { HotKey } from './HotKey'; interface DropdownMenuRadioProps { children: ReactNode; @@ -232,13 +225,19 @@ function DropdownMenuSeparator({ className, ...props }: DropdownMenu.DropdownMen ); } -function DropdownMenuTrigger({ className, ...props }: DropdownMenu.DropdownMenuTriggerProps) { +function DropdownMenuTrigger({ + children, + className, + ...props +}: DropdownMenu.DropdownMenuTriggerProps) { return ( + > + <>{children} + ); } diff --git a/src-web/components/IconButton.tsx b/src-web/components/IconButton.tsx index 607b9c0a..69cee1d9 100644 --- a/src-web/components/IconButton.tsx +++ b/src-web/components/IconButton.tsx @@ -1,16 +1,12 @@ -import { forwardRef } from 'react'; import { Icon, IconProps } from './Icon'; import { Button, ButtonProps } from './Button'; -type Props = Omit & ButtonProps; +type Props = Omit & ButtonProps; -export const IconButton = forwardRef(function IconButton( - { icon, spin, ...props }: Props, - ref, -) { +export function IconButton({ icon, spin, ...props }: Props) { return ( - ); -}); +} diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx index 50172497..8a1317c3 100644 --- a/src-web/components/Sidebar.tsx +++ b/src-web/components/Sidebar.tsx @@ -3,17 +3,19 @@ import classnames from 'classnames'; import { IconButton } from './IconButton'; import { Button } from './Button'; import useTheme from '../hooks/useTheme'; -import { HStack } from './Stacks'; +import { HStack, VStack } from './Stacks'; import { WindowDragRegion } from './WindowDragRegion'; import { Request } from '../hooks/useWorkspaces'; import { invoke } from '@tauri-apps/api'; +import { Link } from 'react-router-dom'; interface Props extends Omit, 'children'> { workspaceId: string; requests: Request[]; + requestId?: string; } -export function Sidebar({ className, workspaceId, requests, ...props }: Props) { +export function Sidebar({ className, requestId, workspaceId, requests, ...props }: Props) { const { toggleTheme } = useTheme(); return (
-
    + {requests.map((r) => ( -
  • +
  • ))} -
+
); } diff --git a/src-web/hooks/useWorkspaces.ts b/src-web/hooks/useWorkspaces.ts index fc8deeff..0bff1250 100644 --- a/src-web/hooks/useWorkspaces.ts +++ b/src-web/hooks/useWorkspaces.ts @@ -27,10 +27,9 @@ export function useWorkspaces(): UseQueryResult { }); } -export function useRequests(): UseQueryResult { +export function useRequests(workspaceId: string): UseQueryResult { return useQuery('requests', async () => { - const workspaces = (await invoke('workspaces')) as Workspace[]; - const requests = (await invoke('requests', { workspaceId: workspaces[0].id })) as Request[]; + const requests = (await invoke('requests', { workspaceId })) as Request[]; return requests.map(convertDates); }); } diff --git a/src-web/main.tsx b/src-web/main.tsx index 052f74ea..1913121c 100644 --- a/src-web/main.tsx +++ b/src-web/main.tsx @@ -1,29 +1,51 @@ 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 init, { greet } from 'hello'; import { invoke } from '@tauri-apps/api'; import { setTheme } from './lib/theme'; - -import './main.css'; import { QueryClient, QueryClientProvider } from 'react-query'; +import { createBrowserRouter, RouterProvider } from 'react-router-dom'; +import { Layout } from './pages/Layout'; +import { Workspaces } from './pages/Workspaces'; +import './main.css'; setTheme(); await init(); greet(); await invoke('load_db'); - const queryClient = new QueryClient(); +const router = createBrowserRouter([ + { + path: '/', + element: , + children: [ + { + path: '/', + element: , + }, + { + path: '/workspaces/:workspaceId', + element: , + }, + { + path: '/workspaces/:workspaceId/requests/:requestId', + element: , + }, + ], + }, +]); + ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - + diff --git a/src-web/pages/Layout.tsx b/src-web/pages/Layout.tsx new file mode 100644 index 00000000..83ec57d9 --- /dev/null +++ b/src-web/pages/Layout.tsx @@ -0,0 +1,9 @@ +import { Outlet } from 'react-router-dom'; + +export function Layout() { + return ( +
+ +
+ ); +} diff --git a/src-web/pages/Workspaces.tsx b/src-web/pages/Workspaces.tsx new file mode 100644 index 00000000..8ad8b965 --- /dev/null +++ b/src-web/pages/Workspaces.tsx @@ -0,0 +1,15 @@ +import { Link, useParams } from 'react-router-dom'; +import { useWorkspaces } from '../hooks/useWorkspaces'; + +export function Workspaces() { + const workspaces = useWorkspaces(); + return ( +
    + {workspaces.data?.map((w) => ( + + {w.name} + + ))} +
+ ); +}