mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-17 06:19:41 +02:00
Better opening workspaces and redirect workspace to recent request
This commit is contained in:
@@ -21,7 +21,9 @@ use sqlx::sqlite::SqlitePoolOptions;
|
|||||||
use sqlx::types::Json;
|
use sqlx::types::Json;
|
||||||
use sqlx::{Pool, Sqlite};
|
use sqlx::{Pool, Sqlite};
|
||||||
use tauri::regex::Regex;
|
use tauri::regex::Regex;
|
||||||
use tauri::{AppHandle, Menu, MenuItem, RunEvent, State, Submenu, TitleBarStyle, Window, Wry};
|
use tauri::{
|
||||||
|
AppHandle, Menu, MenuItem, RunEvent, State, Submenu, TitleBarStyle, Window, WindowUrl, Wry,
|
||||||
|
};
|
||||||
use tauri::{CustomMenuItem, Manager, WindowEvent};
|
use tauri::{CustomMenuItem, Manager, WindowEvent};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
@@ -516,6 +518,12 @@ async fn workspaces(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn new_window(window: Window<Wry>, url: &str) -> Result<(), String> {
|
||||||
|
create_window(&window.app_handle(), Some(url));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn delete_workspace(
|
async fn delete_workspace(
|
||||||
window: Window<Wry>,
|
window: Window<Wry>,
|
||||||
@@ -529,11 +537,6 @@ async fn delete_workspace(
|
|||||||
emit_and_return(&window, "deleted_model", workspace)
|
emit_and_return(&window, "deleted_model", workspace)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn greet(name: &str) -> String {
|
|
||||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
@@ -564,7 +567,7 @@ fn main() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
greet,
|
new_window,
|
||||||
workspaces,
|
workspaces,
|
||||||
get_request,
|
get_request,
|
||||||
requests,
|
requests,
|
||||||
@@ -588,7 +591,7 @@ fn main() {
|
|||||||
.expect("error while running tauri application")
|
.expect("error while running tauri application")
|
||||||
.run(|app_handle, event| match event {
|
.run(|app_handle, event| match event {
|
||||||
RunEvent::Ready => {
|
RunEvent::Ready => {
|
||||||
create_window(app_handle);
|
create_window(app_handle, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExitRequested { api, .. } => {
|
// ExitRequested { api, .. } => {
|
||||||
@@ -602,7 +605,7 @@ fn is_dev() -> bool {
|
|||||||
env.unwrap_or("production") != "production"
|
env.unwrap_or("production") != "production"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_window(handle: &AppHandle<Wry>) -> Window<Wry> {
|
fn create_window(handle: &AppHandle<Wry>, url: Option<&str>) -> Window<Wry> {
|
||||||
let default_menu = Menu::os_default("Yaak".to_string().as_str());
|
let default_menu = Menu::os_default("Yaak".to_string().as_str());
|
||||||
let mut test_menu = Menu::new()
|
let mut test_menu = Menu::new()
|
||||||
.add_item(
|
.add_item(
|
||||||
@@ -661,19 +664,23 @@ fn create_window(handle: &AppHandle<Wry>) -> Window<Wry> {
|
|||||||
let window_num = handle.windows().len();
|
let window_num = handle.windows().len();
|
||||||
let window_id = format!("wnd_{}", window_num);
|
let window_id = format!("wnd_{}", window_num);
|
||||||
let menu = default_menu.add_submenu(submenu);
|
let menu = default_menu.add_submenu(submenu);
|
||||||
let win = tauri::WindowBuilder::new(handle, window_id, tauri::WindowUrl::App("".into()))
|
let win = tauri::WindowBuilder::new(
|
||||||
.menu(menu)
|
handle,
|
||||||
.fullscreen(false)
|
window_id,
|
||||||
.resizable(true)
|
WindowUrl::App(url.unwrap_or_default().into()),
|
||||||
.inner_size(1100.0, 600.0)
|
)
|
||||||
.hidden_title(true)
|
.menu(menu)
|
||||||
.title(match is_dev() {
|
.fullscreen(false)
|
||||||
true => "Yaak Dev",
|
.resizable(true)
|
||||||
false => "Yaak",
|
.inner_size(1100.0, 600.0)
|
||||||
})
|
.hidden_title(true)
|
||||||
.title_bar_style(TitleBarStyle::Overlay)
|
.title(match is_dev() {
|
||||||
.build()
|
true => "Yaak Dev",
|
||||||
.expect("failed to build window");
|
false => "Yaak",
|
||||||
|
})
|
||||||
|
.title_bar_style(TitleBarStyle::Overlay)
|
||||||
|
.build()
|
||||||
|
.expect("failed to build window");
|
||||||
|
|
||||||
let win2 = win.clone();
|
let win2 = win.clone();
|
||||||
let handle2 = handle.clone();
|
let handle2 = handle.clone();
|
||||||
@@ -691,7 +698,7 @@ fn create_window(handle: &AppHandle<Wry>) -> Window<Wry> {
|
|||||||
"toggle_settings" => _ = win2.emit("toggle_settings", true).unwrap(),
|
"toggle_settings" => _ = win2.emit("toggle_settings", true).unwrap(),
|
||||||
"duplicate_request" => _ = win2.emit("duplicate_request", true).unwrap(),
|
"duplicate_request" => _ = win2.emit("duplicate_request", true).unwrap(),
|
||||||
"refresh" => win2.eval("location.reload()").unwrap(),
|
"refresh" => win2.eval("location.reload()").unwrap(),
|
||||||
"new_window" => _ = create_window(&handle2),
|
"new_window" => _ = create_window(&handle2, None),
|
||||||
"toggle_devtools" => {
|
"toggle_devtools" => {
|
||||||
if win2.is_devtools_open() {
|
if win2.is_devtools_open() {
|
||||||
win2.close_devtools();
|
win2.close_devtools();
|
||||||
|
|||||||
@@ -244,6 +244,7 @@ pub async fn upsert_request(
|
|||||||
};
|
};
|
||||||
let headers_json = Json(headers);
|
let headers_json = Json(headers);
|
||||||
let auth_json = Json(authentication);
|
let auth_json = Json(authentication);
|
||||||
|
let trimmed_name = name.trim();
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO http_requests (
|
INSERT INTO http_requests (
|
||||||
@@ -274,7 +275,7 @@ pub async fn upsert_request(
|
|||||||
"#,
|
"#,
|
||||||
id,
|
id,
|
||||||
workspace_id,
|
workspace_id,
|
||||||
name,
|
trimmed_name,
|
||||||
url,
|
url,
|
||||||
method,
|
method,
|
||||||
body,
|
body,
|
||||||
@@ -427,12 +428,13 @@ pub async fn update_workspace(
|
|||||||
workspace: Workspace,
|
workspace: Workspace,
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
) -> Result<Workspace, sqlx::Error> {
|
) -> Result<Workspace, sqlx::Error> {
|
||||||
|
let trimmed_name = workspace.name.trim();
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
UPDATE workspaces SET (name, updated_at) =
|
UPDATE workspaces SET (name, updated_at) =
|
||||||
(?, CURRENT_TIMESTAMP) WHERE id = ?;
|
(?, CURRENT_TIMESTAMP) WHERE id = ?;
|
||||||
"#,
|
"#,
|
||||||
workspace.name,
|
trimmed_name,
|
||||||
workspace.id,
|
workspace.id,
|
||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "Yaak",
|
"productName": "Yaak",
|
||||||
"version": "2023.0.14"
|
"version": "2023.0.15"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"windows": [],
|
"windows": [],
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { createBrowserRouter, Navigate, Outlet, RouterProvider } from 'react-router-dom';
|
import { createBrowserRouter, Navigate, Outlet, RouterProvider } from 'react-router-dom';
|
||||||
import { routePaths } from '../hooks/useAppRoutes';
|
import { routePaths, useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
|
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||||
|
import { useRequests } from '../hooks/useRequests';
|
||||||
import { GlobalHooks } from './GlobalHooks';
|
import { GlobalHooks } from './GlobalHooks';
|
||||||
import RouteError from './RouteError';
|
import RouteError from './RouteError';
|
||||||
import Workspace from './Workspace';
|
import Workspace from './Workspace';
|
||||||
@@ -21,7 +23,7 @@ const router = createBrowserRouter([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: routePaths.workspace({ workspaceId: ':workspaceId' }),
|
path: routePaths.workspace({ workspaceId: ':workspaceId' }),
|
||||||
element: <Workspace />,
|
element: <WorkspaceOrRedirect />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: routePaths.request({
|
path: routePaths.request({
|
||||||
@@ -38,6 +40,23 @@ export function AppRouter() {
|
|||||||
return <RouterProvider router={router} />;
|
return <RouterProvider router={router} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function WorkspaceOrRedirect() {
|
||||||
|
const recentRequests = useRecentRequests();
|
||||||
|
const requests = useRequests();
|
||||||
|
const request = requests.find((r) => r.id === recentRequests[0]);
|
||||||
|
const routes = useAppRoutes();
|
||||||
|
|
||||||
|
if (request === undefined) {
|
||||||
|
return <Workspace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Navigate
|
||||||
|
to={routes.paths.request({ workspaceId: request.workspaceId, requestId: request.id })}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function Layout() {
|
function Layout() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -57,16 +57,21 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
(requestId: string) => {
|
(requestId: string) => {
|
||||||
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 || request.id === activeRequestId) return;
|
if (!request) return;
|
||||||
routes.navigate('request', { requestId, workspaceId: request.workspaceId });
|
routes.navigate('request', { requestId, workspaceId: request.workspaceId });
|
||||||
setSelectedIndex(index);
|
setSelectedIndex(index);
|
||||||
focusActiveRequest(index);
|
focusActiveRequest(index);
|
||||||
},
|
},
|
||||||
[activeRequestId, focusActiveRequest, requests, routes],
|
[focusActiveRequest, requests, routes],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFocus = useCallback(() => focusActiveRequest(), [focusActiveRequest]);
|
const handleFocus = useCallback(() => {
|
||||||
|
if (hasFocus) return;
|
||||||
|
focusActiveRequest(selectedIndex ?? 0);
|
||||||
|
}, [focusActiveRequest, hasFocus, selectedIndex]);
|
||||||
|
|
||||||
const handleBlur = useCallback(() => setHasFocus(false), []);
|
const handleBlur = useCallback(() => setHasFocus(false), []);
|
||||||
|
|
||||||
const handleDeleteKey = useCallback(
|
const handleDeleteKey = useCallback(
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
if (!hasFocus) return;
|
if (!hasFocus) return;
|
||||||
@@ -85,11 +90,11 @@ export const Sidebar = memo(function Sidebar({ className }: Props) {
|
|||||||
useTauriEvent(
|
useTauriEvent(
|
||||||
'focus_sidebar',
|
'focus_sidebar',
|
||||||
() => {
|
() => {
|
||||||
if (hidden) return;
|
if (hidden || hasFocus) return;
|
||||||
// Select 0 index on focus if none selected
|
// Select 0 index on focus if none selected
|
||||||
focusActiveRequest(selectedIndex ?? 0);
|
focusActiveRequest(selectedIndex ?? 0);
|
||||||
},
|
},
|
||||||
[focusActiveRequest, hidden],
|
[focusActiveRequest, hidden, activeRequestId],
|
||||||
);
|
);
|
||||||
|
|
||||||
useKeyPressEvent('Enter', (e) => {
|
useKeyPressEvent('Enter', (e) => {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
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';
|
||||||
@@ -12,6 +13,8 @@ import type { DropdownItem } from './core/Dropdown';
|
|||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
import { Icon } from './core/Icon';
|
import { Icon } from './core/Icon';
|
||||||
import { InlineCode } from './core/InlineCode';
|
import { InlineCode } from './core/InlineCode';
|
||||||
|
import { HStack } from './core/Stacks';
|
||||||
|
import { useDialog } from './DialogContext';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -24,6 +27,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceDropdown({ classN
|
|||||||
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);
|
||||||
|
const dialog = useDialog();
|
||||||
const prompt = usePrompt();
|
const prompt = usePrompt();
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
|
|
||||||
@@ -31,10 +35,46 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceDropdown({ classN
|
|||||||
const workspaceItems = workspaces.map((w) => ({
|
const workspaceItems = workspaces.map((w) => ({
|
||||||
key: w.id,
|
key: w.id,
|
||||||
label: w.name,
|
label: w.name,
|
||||||
leftSlot: activeWorkspaceId === w.id ? <Icon icon="check" /> : <Icon icon="empty" />,
|
onSelect: async () => {
|
||||||
onSelect: () => {
|
dialog.show({
|
||||||
if (w.id === activeWorkspaceId) return;
|
id: 'open-workspace',
|
||||||
routes.navigate('workspace', { workspaceId: w.id });
|
size: 'sm',
|
||||||
|
title: 'Open Workspace',
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
Where would you like to open <InlineCode>{w.name}</InlineCode>?
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
render: ({ hide }) => {
|
||||||
|
return (
|
||||||
|
<HStack space={2} justifyContent="end" className="mt-6">
|
||||||
|
<Button
|
||||||
|
className="focus"
|
||||||
|
color="gray"
|
||||||
|
rightSlot={<Icon icon="openNewWindow" />}
|
||||||
|
onClick={async () => {
|
||||||
|
hide();
|
||||||
|
await invoke('new_window', {
|
||||||
|
url: routes.paths.workspace({ workspaceId: w.id }),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
New Window
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="focus"
|
||||||
|
color="gray"
|
||||||
|
onClick={() => {
|
||||||
|
hide();
|
||||||
|
routes.navigate('workspace', { workspaceId: w.id });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
This Window
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import type { HTMLAttributes } from 'react';
|
import type { HTMLAttributes, ReactNode } from 'react';
|
||||||
import { forwardRef, memo, useMemo } from 'react';
|
import { forwardRef, memo, useMemo } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { Icon } from './Icon';
|
import { Icon } from './Icon';
|
||||||
|
|
||||||
const colorStyles = {
|
const colorStyles = {
|
||||||
@@ -15,8 +14,7 @@ const colorStyles = {
|
|||||||
danger: 'bg-red-400 text-white enabled:hocus:bg-red-500 ring-red-500/50',
|
danger: 'bg-red-400 text-white enabled:hocus:bg-red-500 ring-red-500/50',
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ButtonProps = HTMLAttributes<HTMLElement> & {
|
export type ButtonProps = HTMLAttributes<HTMLButtonElement> & {
|
||||||
to?: string;
|
|
||||||
color?: keyof typeof colorStyles;
|
color?: keyof typeof colorStyles;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
size?: 'sm' | 'md' | 'xs';
|
size?: 'sm' | 'md' | 'xs';
|
||||||
@@ -25,12 +23,12 @@ export type ButtonProps = HTMLAttributes<HTMLElement> & {
|
|||||||
forDropdown?: boolean;
|
forDropdown?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
rightSlot?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const _Button = forwardRef<any, ButtonProps>(function Button(
|
const _Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
|
||||||
{
|
{
|
||||||
to,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
@@ -39,6 +37,7 @@ const _Button = forwardRef<any, ButtonProps>(function Button(
|
|||||||
type = 'button',
|
type = 'button',
|
||||||
justify = 'center',
|
justify = 'center',
|
||||||
size = 'md',
|
size = 'md',
|
||||||
|
rightSlot,
|
||||||
disabled,
|
disabled,
|
||||||
...props
|
...props
|
||||||
}: ButtonProps,
|
}: ButtonProps,
|
||||||
@@ -48,7 +47,7 @@ const _Button = forwardRef<any, ButtonProps>(function Button(
|
|||||||
() =>
|
() =>
|
||||||
classnames(
|
classnames(
|
||||||
className,
|
className,
|
||||||
'outline-none whitespace-nowrap',
|
'flex-shrink-0 outline-none whitespace-nowrap',
|
||||||
'focus-visible-or-class:ring',
|
'focus-visible-or-class:ring',
|
||||||
'rounded-md flex items-center',
|
'rounded-md flex items-center',
|
||||||
disabled ? 'pointer-events-none opacity-disabled' : 'pointer-events-auto',
|
disabled ? 'pointer-events-none opacity-disabled' : 'pointer-events-auto',
|
||||||
@@ -62,22 +61,14 @@ const _Button = forwardRef<any, ButtonProps>(function Button(
|
|||||||
[className, disabled, color, justify, size],
|
[className, disabled, color, justify, size],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (typeof to === 'string') {
|
return (
|
||||||
return (
|
<button ref={ref} type={type} className={classes} disabled={disabled} {...props}>
|
||||||
<Link ref={ref} to={to} className={classes} {...props}>
|
{isLoading && <Icon icon="update" size={size} className="animate-spin mr-1" />}
|
||||||
{children}
|
{children}
|
||||||
{forDropdown && <Icon icon="chevronDown" className="ml-1 -mr-1" />}
|
{rightSlot && <div className="ml-1">{rightSlot}</div>}
|
||||||
</Link>
|
{forDropdown && <Icon icon="chevronDown" size={size} className="ml-1 -mr-1" />}
|
||||||
);
|
</button>
|
||||||
} else {
|
);
|
||||||
return (
|
|
||||||
<button ref={ref} type={type} className={classes} disabled={disabled} {...props}>
|
|
||||||
{isLoading && <Icon icon="update" size={size} className="animate-spin mr-1" />}
|
|
||||||
{children}
|
|
||||||
{forDropdown && <Icon icon="chevronDown" size={size} className="ml-1 -mr-1" />}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Button = memo(_Button);
|
export const Button = memo(_Button);
|
||||||
|
|||||||
@@ -62,6 +62,13 @@ export function Dialog({
|
|||||||
size === 'dynamic' && 'min-w-[30vw] max-w-[80vw]',
|
size === 'dynamic' && 'min-w-[30vw] max-w-[80vw]',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
<Heading className="text-xl font-semibold w-full" id={titleId}>
|
||||||
|
{title}
|
||||||
|
</Heading>
|
||||||
|
{description && <p id={descriptionId}>{description}</p>}
|
||||||
|
<div className="mt-4">{children}</div>
|
||||||
|
|
||||||
|
{/*Put close at the end so that it's the last thing to be tabbed to*/}
|
||||||
{!hideX && (
|
{!hideX && (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
@@ -72,11 +79,6 @@ export function Dialog({
|
|||||||
className="ml-auto absolute right-1 top-1"
|
className="ml-auto absolute right-1 top-1"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Heading className="text-xl font-semibold w-full" id={titleId}>
|
|
||||||
{title}
|
|
||||||
</Heading>
|
|
||||||
{description && <p id={descriptionId}>{description}</p>}
|
|
||||||
<div className="mt-4">{children}</div>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import React, {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import { useKey, useKeyPressEvent } from 'react-use';
|
import { useKey, useKeyPressEvent } from 'react-use';
|
||||||
import { Portal } from '../Portal';
|
import { Portal } from '../Portal';
|
||||||
|
import { Button } from './Button';
|
||||||
import { Separator } from './Separator';
|
import { Separator } from './Separator';
|
||||||
import { VStack } from './Stacks';
|
import { VStack } from './Stacks';
|
||||||
|
|
||||||
@@ -346,16 +347,18 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
|||||||
if (item.type === 'separator') return <Separator className="my-1.5" />;
|
if (item.type === 'separator') return <Separator className="my-1.5" />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<Button
|
||||||
ref={initRef}
|
ref={initRef}
|
||||||
|
size="xs"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
onMouseEnter={(e) => e.currentTarget.focus()}
|
onMouseEnter={(e) => e.currentTarget.focus()}
|
||||||
onMouseLeave={(e) => e.currentTarget.blur()}
|
onMouseLeave={(e) => e.currentTarget.blur()}
|
||||||
onFocus={handleFocus}
|
onFocus={handleFocus}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
justify="start"
|
||||||
className={classnames(
|
className={classnames(
|
||||||
className,
|
className,
|
||||||
'min-w-[8rem] outline-none px-2 mx-1.5 h-7 flex items-center 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',
|
||||||
item.variant === 'danger' && 'text-red-600',
|
item.variant === 'danger' && 'text-red-600',
|
||||||
)}
|
)}
|
||||||
@@ -371,6 +374,6 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
|||||||
{item.label}
|
{item.label}
|
||||||
</div>
|
</div>
|
||||||
{item.rightSlot && <div className="ml-auto pl-3">{item.rightSlot}</div>}
|
{item.rightSlot && <div className="ml-auto pl-3">{item.rightSlot}</div>}
|
||||||
</button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
MagicWandIcon,
|
MagicWandIcon,
|
||||||
MagnifyingGlassIcon,
|
MagnifyingGlassIcon,
|
||||||
MoonIcon,
|
MoonIcon,
|
||||||
|
OpenInNewWindowIcon,
|
||||||
PaperPlaneIcon,
|
PaperPlaneIcon,
|
||||||
Pencil2Icon,
|
Pencil2Icon,
|
||||||
PlusCircledIcon,
|
PlusCircledIcon,
|
||||||
@@ -66,6 +67,7 @@ const icons = {
|
|||||||
magicWand: MagicWandIcon,
|
magicWand: MagicWandIcon,
|
||||||
magnifyingGlass: MagnifyingGlassIcon,
|
magnifyingGlass: MagnifyingGlassIcon,
|
||||||
moon: MoonIcon,
|
moon: MoonIcon,
|
||||||
|
openNewWindow: OpenInNewWindowIcon,
|
||||||
paperPlane: PaperPlaneIcon,
|
paperPlane: PaperPlaneIcon,
|
||||||
pencil: Pencil2Icon,
|
pencil: Pencil2Icon,
|
||||||
plus: PlusIcon,
|
plus: PlusIcon,
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { createGlobalState, useEffectOnce, useLocalStorage } from 'react-use';
|
import { createGlobalState, useEffectOnce, useLocalStorage } from 'react-use';
|
||||||
import { useActiveRequestId } from './useActiveRequestId';
|
import { useActiveRequestId } from './useActiveRequestId';
|
||||||
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
|
|
||||||
const useHistoryState = createGlobalState<string[]>([]);
|
const useHistoryState = createGlobalState<string[]>([]);
|
||||||
|
|
||||||
export function useRecentRequests() {
|
export function useRecentRequests() {
|
||||||
const [history, setHistory] = useHistoryState();
|
const activeWorkspaceId = useActiveWorkspaceId();
|
||||||
const activeRequestId = useActiveRequestId();
|
const activeRequestId = useActiveRequestId();
|
||||||
const [lsState, setLSState] = useLocalStorage<string[]>('recent_requests', []);
|
const [history, setHistory] = useHistoryState();
|
||||||
|
const [lsState, setLSState] = useLocalStorage<string[]>(
|
||||||
|
'recent_requests::' + activeWorkspaceId,
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLSState(history);
|
setLSState(history);
|
||||||
|
|||||||
Reference in New Issue
Block a user