mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 01:08:28 +02:00
Start of command palette
This commit is contained in:
@@ -5,8 +5,8 @@ function u(r) {
|
|||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (t(e) && "yaakSchema" in e && (e.yaakSchema === 1 && (e.resources.httpRequests = e.resources.requests, e.yaakSchema = 2), e.yaakSchema === 2))
|
if (!(!t(e) || !("yaakSchema" in e)))
|
||||||
return { resources: e.resources };
|
return "requests" in e.resources && (e.resources.httpRequests = e.resources.requests, delete e.resources.requests), { resources: e.resources };
|
||||||
}
|
}
|
||||||
function t(r) {
|
function t(r) {
|
||||||
return Object.prototype.toString.call(r) === "[object Object]";
|
return Object.prototype.toString.call(r) === "[object Object]";
|
||||||
|
|||||||
@@ -56,18 +56,6 @@ mod updates;
|
|||||||
mod window_ext;
|
mod window_ext;
|
||||||
mod window_menu;
|
mod window_menu;
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
|
||||||
pub struct CustomResponse {
|
|
||||||
status: u16,
|
|
||||||
body: String,
|
|
||||||
url: String,
|
|
||||||
method: String,
|
|
||||||
elapsed: u128,
|
|
||||||
elapsed2: u128,
|
|
||||||
headers: HashMap<String, String>,
|
|
||||||
pub status_reason: Option<&'static str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn migrate_db(app_handle: AppHandle, db: &Mutex<Pool<Sqlite>>) -> Result<(), String> {
|
async fn migrate_db(app_handle: AppHandle, db: &Mutex<Pool<Sqlite>>) -> Result<(), String> {
|
||||||
let pool = &*db.lock().await;
|
let pool = &*db.lock().await;
|
||||||
let p = app_handle
|
let p = app_handle
|
||||||
@@ -82,6 +70,26 @@ async fn migrate_db(app_handle: AppHandle, db: &Mutex<Pool<Sqlite>>) -> Result<(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
#[serde(default, rename_all = "camelCase")]
|
||||||
|
struct AppMetaData {
|
||||||
|
is_dev: bool,
|
||||||
|
version: String,
|
||||||
|
name: String,
|
||||||
|
app_data_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn cmd_metadata(app_handle: AppHandle) -> Result<AppMetaData, ()> {
|
||||||
|
let p = app_handle.path_resolver();
|
||||||
|
return Ok(AppMetaData{
|
||||||
|
is_dev: is_dev(),
|
||||||
|
version: app_handle.package_info().version.to_string(),
|
||||||
|
name: app_handle.package_info().name.to_string(),
|
||||||
|
app_data_dir: p.app_data_dir().unwrap().to_string_lossy().to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_grpc_reflect(
|
async fn cmd_grpc_reflect(
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
@@ -1442,26 +1450,26 @@ fn main() {
|
|||||||
cmd_create_grpc_request,
|
cmd_create_grpc_request,
|
||||||
cmd_create_http_request,
|
cmd_create_http_request,
|
||||||
cmd_create_workspace,
|
cmd_create_workspace,
|
||||||
cmd_delete_all_http_responses,
|
|
||||||
cmd_delete_all_grpc_connections,
|
cmd_delete_all_grpc_connections,
|
||||||
|
cmd_delete_all_http_responses,
|
||||||
cmd_delete_cookie_jar,
|
cmd_delete_cookie_jar,
|
||||||
cmd_delete_environment,
|
cmd_delete_environment,
|
||||||
cmd_delete_folder,
|
cmd_delete_folder,
|
||||||
cmd_delete_grpc_request,
|
|
||||||
cmd_delete_grpc_connection,
|
cmd_delete_grpc_connection,
|
||||||
|
cmd_delete_grpc_request,
|
||||||
cmd_delete_http_request,
|
cmd_delete_http_request,
|
||||||
cmd_delete_http_response,
|
cmd_delete_http_response,
|
||||||
cmd_delete_workspace,
|
cmd_delete_workspace,
|
||||||
cmd_duplicate_http_request,
|
|
||||||
cmd_duplicate_grpc_request,
|
cmd_duplicate_grpc_request,
|
||||||
|
cmd_duplicate_http_request,
|
||||||
cmd_export_data,
|
cmd_export_data,
|
||||||
cmd_filter_response,
|
cmd_filter_response,
|
||||||
cmd_get_cookie_jar,
|
cmd_get_cookie_jar,
|
||||||
cmd_get_environment,
|
cmd_get_environment,
|
||||||
cmd_get_folder,
|
cmd_get_folder,
|
||||||
cmd_get_key_value,
|
|
||||||
cmd_get_http_request,
|
|
||||||
cmd_get_grpc_request,
|
cmd_get_grpc_request,
|
||||||
|
cmd_get_http_request,
|
||||||
|
cmd_get_key_value,
|
||||||
cmd_get_settings,
|
cmd_get_settings,
|
||||||
cmd_get_workspace,
|
cmd_get_workspace,
|
||||||
cmd_grpc_go,
|
cmd_grpc_go,
|
||||||
@@ -1470,12 +1478,13 @@ fn main() {
|
|||||||
cmd_list_cookie_jars,
|
cmd_list_cookie_jars,
|
||||||
cmd_list_environments,
|
cmd_list_environments,
|
||||||
cmd_list_folders,
|
cmd_list_folders,
|
||||||
cmd_list_http_requests,
|
|
||||||
cmd_list_grpc_requests,
|
|
||||||
cmd_list_grpc_connections,
|
cmd_list_grpc_connections,
|
||||||
cmd_list_grpc_events,
|
cmd_list_grpc_events,
|
||||||
|
cmd_list_grpc_requests,
|
||||||
|
cmd_list_http_requests,
|
||||||
cmd_list_http_responses,
|
cmd_list_http_responses,
|
||||||
cmd_list_workspaces,
|
cmd_list_workspaces,
|
||||||
|
cmd_metadata,
|
||||||
cmd_new_window,
|
cmd_new_window,
|
||||||
cmd_send_ephemeral_request,
|
cmd_send_ephemeral_request,
|
||||||
cmd_send_http_request,
|
cmd_send_http_request,
|
||||||
|
|||||||
59
src-web/components/CommandPalette.tsx
Normal file
59
src-web/components/CommandPalette.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import { useRequests } from '../hooks/useRequests';
|
||||||
|
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||||
|
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||||
|
import { Input } from './core/Input';
|
||||||
|
|
||||||
|
export function CommandPalette() {
|
||||||
|
const [selectedIndex, setSelectedIndex] = useState<number>(0);
|
||||||
|
const workspaces = useWorkspaces();
|
||||||
|
const requests = useRequests();
|
||||||
|
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'ArrowDown') {
|
||||||
|
setSelectedIndex((prev) => prev + 1);
|
||||||
|
} else if (e.key === 'ArrowUp') {
|
||||||
|
setSelectedIndex((prev) => prev - 1);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full grid grid-rows-[auto_minmax(0,1fr)]">
|
||||||
|
<div className="px-2 py-2 w-full">
|
||||||
|
<Input
|
||||||
|
hideLabel
|
||||||
|
name="command"
|
||||||
|
label="Command"
|
||||||
|
placeholder="Type a command"
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="h-full px-1.5 overflow-y-auto">
|
||||||
|
{requests.map((r, i) => (
|
||||||
|
<CommandPaletteItem active={i === selectedIndex} key={r.id}>
|
||||||
|
Switch Request → {fallbackRequestName(r)}
|
||||||
|
</CommandPaletteItem>
|
||||||
|
))}
|
||||||
|
{workspaces.map((w, i) => (
|
||||||
|
<CommandPaletteItem active={i === selectedIndex} key={w.id}>
|
||||||
|
Switch Workspace → {w.name}
|
||||||
|
</CommandPaletteItem>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandPaletteItem({ children, active }: { children: ReactNode; active: boolean }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'h-xs flex items-center rounded px-1.5 text-gray-600',
|
||||||
|
active && 'bg-highlightSecondary text-gray-800',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import { Dialog } from './core/Dialog';
|
|||||||
type DialogEntry = {
|
type DialogEntry = {
|
||||||
id: string;
|
id: string;
|
||||||
render: ({ hide }: { hide: () => void }) => React.ReactNode;
|
render: ({ hide }: { hide: () => void }) => React.ReactNode;
|
||||||
} & Pick<DialogProps, 'title' | 'description' | 'hideX' | 'className' | 'size' | 'noPadding'>;
|
} & Omit<DialogProps, 'onClose' | 'open' | 'children'>;
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
dialogs: DialogEntry[];
|
dialogs: DialogEntry[];
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useQueryClient } from '@tanstack/react-query';
|
|||||||
import { appWindow } from '@tauri-apps/api/window';
|
import { appWindow } from '@tauri-apps/api/window';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { useCommandPalette } from '../hooks/useCommandPalette';
|
||||||
import { cookieJarsQueryKey } from '../hooks/useCookieJars';
|
import { cookieJarsQueryKey } from '../hooks/useCookieJars';
|
||||||
import { useGlobalCommands } from '../hooks/useGlobalCommands';
|
import { useGlobalCommands } from '../hooks/useGlobalCommands';
|
||||||
import { grpcConnectionsQueryKey } from '../hooks/useGrpcConnections';
|
import { grpcConnectionsQueryKey } from '../hooks/useGrpcConnections';
|
||||||
@@ -35,6 +36,7 @@ export function GlobalHooks() {
|
|||||||
useSyncAppearance();
|
useSyncAppearance();
|
||||||
useSyncWindowTitle();
|
useSyncWindowTitle();
|
||||||
useGlobalCommands();
|
useGlobalCommands();
|
||||||
|
useCommandPalette();
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
const { wasUpdatedExternally } = useRequestUpdateKey(null);
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export function GrpcConnectionSetupPane({
|
|||||||
const handleSend = useCallback(async () => {
|
const handleSend = useCallback(async () => {
|
||||||
if (activeRequest == null) return;
|
if (activeRequest == null) return;
|
||||||
onSend({ message: activeRequest.message });
|
onSend({ message: activeRequest.message });
|
||||||
}, [activeRequest, onGo]);
|
}, [activeRequest, onSend]);
|
||||||
|
|
||||||
const tabs: TabItem[] = useMemo(
|
const tabs: TabItem[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { useKey, useKeyPressEvent } from 'react-use';
|
import { useKeyPressEvent } from 'react-use';
|
||||||
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||||
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
|
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
|
||||||
import { useAppRoutes } from '../hooks/useAppRoutes';
|
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
import { useGrpcRequests } from '../hooks/useGrpcRequests';
|
|
||||||
import { useHotKey } from '../hooks/useHotKey';
|
import { useHotKey } from '../hooks/useHotKey';
|
||||||
import { useHttpRequests } from '../hooks/useHttpRequests';
|
|
||||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||||
|
import { useRequests } from '../hooks/useRequests';
|
||||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||||
import type { ButtonProps } from './core/Button';
|
import type { ButtonProps } from './core/Button';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
@@ -21,20 +20,10 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
|
|||||||
const activeRequest = useActiveRequest();
|
const activeRequest = useActiveRequest();
|
||||||
const activeWorkspaceId = useActiveWorkspaceId();
|
const activeWorkspaceId = useActiveWorkspaceId();
|
||||||
const activeEnvironment = useActiveEnvironment();
|
const activeEnvironment = useActiveEnvironment();
|
||||||
const httpRequests = useHttpRequests();
|
|
||||||
const grpcRequests = useGrpcRequests();
|
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
const allRecentRequestIds = useRecentRequests();
|
const allRecentRequestIds = useRecentRequests();
|
||||||
const recentRequestIds = useMemo(() => allRecentRequestIds.slice(1), [allRecentRequestIds]);
|
const recentRequestIds = useMemo(() => allRecentRequestIds.slice(1), [allRecentRequestIds]);
|
||||||
const requests = useMemo(() => [...httpRequests, ...grpcRequests], [httpRequests, grpcRequests]);
|
const requests = useRequests();
|
||||||
|
|
||||||
// Toggle the menu on Cmd+k
|
|
||||||
useKey('k', (e) => {
|
|
||||||
if (e.metaKey) {
|
|
||||||
e.preventDefault();
|
|
||||||
dropdownRef.current?.toggle();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle key-up
|
// Handle key-up
|
||||||
useKeyPressEvent('Control', undefined, () => {
|
useKeyPressEvent('Control', undefined, () => {
|
||||||
@@ -42,16 +31,20 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
|
|||||||
dropdownRef.current?.select?.();
|
dropdownRef.current?.select?.();
|
||||||
});
|
});
|
||||||
|
|
||||||
useHotKey('requestSwitcher.prev', () => {
|
useHotKey('request_switcher.prev', () => {
|
||||||
if (!dropdownRef.current?.isOpen) dropdownRef.current?.open();
|
if (!dropdownRef.current?.isOpen) dropdownRef.current?.open();
|
||||||
dropdownRef.current?.next?.();
|
dropdownRef.current?.next?.();
|
||||||
});
|
});
|
||||||
|
|
||||||
useHotKey('requestSwitcher.next', () => {
|
useHotKey('request_switcher.next', () => {
|
||||||
if (!dropdownRef.current?.isOpen) dropdownRef.current?.open();
|
if (!dropdownRef.current?.isOpen) dropdownRef.current?.open();
|
||||||
dropdownRef.current?.prev?.();
|
dropdownRef.current?.prev?.();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useHotKey('request_switcher.toggle', () => {
|
||||||
|
dropdownRef.current?.toggle();
|
||||||
|
});
|
||||||
|
|
||||||
const items = useMemo<DropdownItem[]>(() => {
|
const items = useMemo<DropdownItem[]>(() => {
|
||||||
if (activeWorkspaceId === null) return [];
|
if (activeWorkspaceId === null) return [];
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ export function RedirectToLatestWorkspace() {
|
|||||||
const recentWorkspaces = useRecentWorkspaces();
|
const recentWorkspaces = useRecentWorkspaces();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (workspaces.length === 0) {
|
||||||
|
console.log('No workspaces found to redirect to. Skipping.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
(async function () {
|
(async function () {
|
||||||
const workspaceId = recentWorkspaces[0] ?? workspaces[0]?.id ?? 'n/a';
|
const workspaceId = recentWorkspaces[0] ?? workspaces[0]?.id ?? 'n/a';
|
||||||
const environmentId = (await getRecentEnvironments(workspaceId))[0];
|
const environmentId = (await getRecentEnvironments(workspaceId))[0];
|
||||||
|
|||||||
@@ -61,6 +61,25 @@ export const RequestPane = memo(function RequestPane({
|
|||||||
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
|
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
|
||||||
const contentType = useContentTypeFromHeaders(activeRequest.headers);
|
const contentType = useContentTypeFromHeaders(activeRequest.headers);
|
||||||
|
|
||||||
|
const handleContentTypeChange = useCallback(
|
||||||
|
async (contentType: string | null) => {
|
||||||
|
const headers = activeRequest.headers.filter((h) => h.name.toLowerCase() !== 'content-type');
|
||||||
|
|
||||||
|
if (contentType != null) {
|
||||||
|
headers.push({
|
||||||
|
name: 'Content-Type',
|
||||||
|
value: contentType,
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await updateRequest.mutateAsync({ headers });
|
||||||
|
|
||||||
|
// Force update header editor so any changed headers are reflected
|
||||||
|
setTimeout(() => setForceUpdateHeaderEditorKey((u) => u + 1), 100);
|
||||||
|
},
|
||||||
|
[activeRequest.headers, updateRequest],
|
||||||
|
);
|
||||||
|
|
||||||
const tabs: TabItem[] = useMemo(
|
const tabs: TabItem[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@@ -153,7 +172,15 @@ export const RequestPane = memo(function RequestPane({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[activeRequest, updateRequest],
|
[
|
||||||
|
activeRequest.authentication,
|
||||||
|
activeRequest.authenticationType,
|
||||||
|
activeRequest.bodyType,
|
||||||
|
activeRequest.headers,
|
||||||
|
activeRequest.urlParameters,
|
||||||
|
handleContentTypeChange,
|
||||||
|
updateRequest,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleBodyChange = useCallback(
|
const handleBodyChange = useCallback(
|
||||||
@@ -161,24 +188,6 @@ export const RequestPane = memo(function RequestPane({
|
|||||||
[updateRequest],
|
[updateRequest],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleContentTypeChange = useCallback(
|
|
||||||
async (contentType: string | null) => {
|
|
||||||
const headers = activeRequest.headers.filter((h) => h.name.toLowerCase() !== 'content-type');
|
|
||||||
|
|
||||||
if (contentType != null) {
|
|
||||||
headers.push({
|
|
||||||
name: 'Content-Type',
|
|
||||||
value: contentType,
|
|
||||||
enabled: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await updateRequest.mutateAsync({ headers });
|
|
||||||
|
|
||||||
// Force update header editor so any changed headers are reflected
|
|
||||||
setTimeout(() => setForceUpdateHeaderEditorKey((u) => u + 1), 100);
|
|
||||||
},
|
|
||||||
[activeRequest.headers, updateRequest],
|
|
||||||
);
|
|
||||||
const handleBinaryFileChange = useCallback(
|
const handleBinaryFileChange = useCallback(
|
||||||
(body: HttpRequest['body']) => {
|
(body: HttpRequest['body']) => {
|
||||||
updateRequest.mutate({ body });
|
updateRequest.mutate({ body });
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export interface DialogProps {
|
|||||||
size?: 'sm' | 'md' | 'lg' | 'full' | 'dynamic';
|
size?: 'sm' | 'md' | 'lg' | 'full' | 'dynamic';
|
||||||
hideX?: boolean;
|
hideX?: boolean;
|
||||||
noPadding?: boolean;
|
noPadding?: boolean;
|
||||||
|
noScroll?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Dialog({
|
export function Dialog({
|
||||||
@@ -29,6 +30,7 @@ export function Dialog({
|
|||||||
description,
|
description,
|
||||||
hideX,
|
hideX,
|
||||||
noPadding,
|
noPadding,
|
||||||
|
noScroll,
|
||||||
}: DialogProps) {
|
}: DialogProps) {
|
||||||
const titleId = useMemo(() => Math.random().toString(36).slice(2), []);
|
const titleId = useMemo(() => Math.random().toString(36).slice(2), []);
|
||||||
const descriptionId = useMemo(
|
const descriptionId = useMemo(
|
||||||
@@ -60,7 +62,7 @@ export function Dialog({
|
|||||||
animate={{ top: 0, scale: 1 }}
|
animate={{ top: 0, scale: 1 }}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'grid grid-rows-[auto_minmax(0,1fr)]',
|
'h-full grid grid-rows-[auto_auto_minmax(0,1fr)]',
|
||||||
'relative bg-gray-50 pointer-events-auto',
|
'relative bg-gray-50 pointer-events-auto',
|
||||||
'rounded-lg',
|
'rounded-lg',
|
||||||
'dark:border border-highlight shadow shadow-black/10',
|
'dark:border border-highlight shadow shadow-black/10',
|
||||||
@@ -79,15 +81,20 @@ export function Dialog({
|
|||||||
) : (
|
) : (
|
||||||
<span />
|
<span />
|
||||||
)}
|
)}
|
||||||
{description && (
|
|
||||||
|
{description ? (
|
||||||
<p className="px-6 text-gray-700" id={descriptionId}>
|
<p className="px-6 text-gray-700" id={descriptionId}>
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'h-full w-full grid grid-cols-[minmax(0,1fr)] overflow-y-auto',
|
'h-full w-full grid grid-cols-[minmax(0,1fr)]',
|
||||||
!noPadding && 'px-6 py-2',
|
!noPadding && 'px-6 py-2',
|
||||||
|
!noScroll && 'overflow-y-auto',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import * as app from '@tauri-apps/api/app';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import * as path from '@tauri-apps/api/path';
|
|
||||||
|
|
||||||
export function useAppInfo() {
|
export function useAppInfo() {
|
||||||
return useQuery(['appInfo'], async () => {
|
return useQuery(['appInfo'], async () => {
|
||||||
const [version, appDataDir] = await Promise.all([app.getVersion(), path.appDataDir()]);
|
return (await invoke('cmd_metadata')) as {
|
||||||
return { version, appDataDir };
|
isDev: boolean;
|
||||||
|
version: string;
|
||||||
|
name: string;
|
||||||
|
appDataDir: string;
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
24
src-web/hooks/useCommandPalette.tsx
Normal file
24
src-web/hooks/useCommandPalette.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { CommandPalette } from '../components/CommandPalette';
|
||||||
|
import { useDialog } from '../components/DialogContext';
|
||||||
|
import { useAppInfo } from './useAppInfo';
|
||||||
|
import { useHotKey } from './useHotKey';
|
||||||
|
|
||||||
|
export function useCommandPalette() {
|
||||||
|
const dialog = useDialog();
|
||||||
|
const appInfo = useAppInfo();
|
||||||
|
useHotKey('command_palette.toggle', () => {
|
||||||
|
// Disabled in production for now
|
||||||
|
if (!appInfo.data?.isDev) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.toggle({
|
||||||
|
id: 'command_palette',
|
||||||
|
size: 'md',
|
||||||
|
hideX: true,
|
||||||
|
noPadding: true,
|
||||||
|
noScroll: true,
|
||||||
|
render: () => <CommandPalette />,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -13,12 +13,14 @@ export type HotkeyAction =
|
|||||||
| 'http_request.create'
|
| 'http_request.create'
|
||||||
| 'http_request.duplicate'
|
| 'http_request.duplicate'
|
||||||
| 'http_request.send'
|
| 'http_request.send'
|
||||||
| 'requestSwitcher.next'
|
| 'request_switcher.next'
|
||||||
| 'requestSwitcher.prev'
|
| 'request_switcher.prev'
|
||||||
|
| 'request_switcher.toggle'
|
||||||
| 'settings.show'
|
| 'settings.show'
|
||||||
| 'sidebar.focus'
|
| 'sidebar.focus'
|
||||||
| 'sidebar.toggle'
|
| 'sidebar.toggle'
|
||||||
| 'urlBar.focus';
|
| 'urlBar.focus'
|
||||||
|
| 'command_palette.toggle';
|
||||||
|
|
||||||
const hotkeys: Record<HotkeyAction, string[]> = {
|
const hotkeys: Record<HotkeyAction, string[]> = {
|
||||||
'environmentEditor.toggle': ['CmdCtrl+Shift+e'],
|
'environmentEditor.toggle': ['CmdCtrl+Shift+e'],
|
||||||
@@ -27,12 +29,14 @@ const hotkeys: Record<HotkeyAction, string[]> = {
|
|||||||
'http_request.create': ['CmdCtrl+n'],
|
'http_request.create': ['CmdCtrl+n'],
|
||||||
'http_request.duplicate': ['CmdCtrl+d'],
|
'http_request.duplicate': ['CmdCtrl+d'],
|
||||||
'http_request.send': ['CmdCtrl+Enter', 'CmdCtrl+r'],
|
'http_request.send': ['CmdCtrl+Enter', 'CmdCtrl+r'],
|
||||||
'requestSwitcher.next': ['Control+Shift+Tab'],
|
'request_switcher.next': ['Control+Shift+Tab'],
|
||||||
'requestSwitcher.prev': ['Control+Tab'],
|
'request_switcher.prev': ['Control+Tab'],
|
||||||
|
'request_switcher.toggle': ['CmdCtrl+p'],
|
||||||
'settings.show': ['CmdCtrl+,'],
|
'settings.show': ['CmdCtrl+,'],
|
||||||
'sidebar.focus': ['CmdCtrl+1'],
|
'sidebar.focus': ['CmdCtrl+1'],
|
||||||
'sidebar.toggle': ['CmdCtrl+b'],
|
'sidebar.toggle': ['CmdCtrl+b'],
|
||||||
'urlBar.focus': ['CmdCtrl+l'],
|
'urlBar.focus': ['CmdCtrl+l'],
|
||||||
|
'command_palette.toggle': ['CmdCtrl+k'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const hotkeyLabels: Record<HotkeyAction, string> = {
|
const hotkeyLabels: Record<HotkeyAction, string> = {
|
||||||
@@ -42,12 +46,14 @@ const hotkeyLabels: Record<HotkeyAction, string> = {
|
|||||||
'http_request.create': 'New Request',
|
'http_request.create': 'New Request',
|
||||||
'http_request.duplicate': 'Duplicate Request',
|
'http_request.duplicate': 'Duplicate Request',
|
||||||
'http_request.send': 'Send Request',
|
'http_request.send': 'Send Request',
|
||||||
'requestSwitcher.next': 'Go To Previous Request',
|
'request_switcher.next': 'Go To Previous Request',
|
||||||
'requestSwitcher.prev': 'Go To Next Request',
|
'request_switcher.prev': 'Go To Next Request',
|
||||||
|
'request_switcher.toggle': 'Toggle Request Switcher',
|
||||||
'settings.show': 'Open Settings',
|
'settings.show': 'Open Settings',
|
||||||
'sidebar.focus': 'Focus Sidebar',
|
'sidebar.focus': 'Focus Sidebar',
|
||||||
'sidebar.toggle': 'Toggle Sidebar',
|
'sidebar.toggle': 'Toggle Sidebar',
|
||||||
'urlBar.focus': 'Focus URL',
|
'urlBar.focus': 'Focus URL',
|
||||||
|
'command_palette.toggle': 'Toggle Command Palette',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hotkeyActions: HotkeyAction[] = Object.keys(hotkeys) as (keyof typeof hotkeys)[];
|
export const hotkeyActions: HotkeyAction[] = Object.keys(hotkeys) as (keyof typeof hotkeys)[];
|
||||||
@@ -135,7 +141,7 @@ export function useHotKey(
|
|||||||
document.removeEventListener('keydown', down, { capture: true });
|
document.removeEventListener('keydown', down, { capture: true });
|
||||||
document.removeEventListener('keyup', up, { capture: true });
|
document.removeEventListener('keyup', up, { capture: true });
|
||||||
};
|
};
|
||||||
}, [options.enable, os]);
|
}, [action, options.enable, os]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useHotKeyLabel(action: HotkeyAction): string {
|
export function useHotKeyLabel(action: HotkeyAction): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user