mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-17 23:14:03 +01:00
Show hotkeys on empty views
This commit is contained in:
@@ -35,7 +35,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo
|
||||
});
|
||||
}, [dialog, activeEnvironment]);
|
||||
|
||||
useHotkey('environmentEditor.toggle', showEnvironmentDialog);
|
||||
useHotkey('environmentEditor.toggle', showEnvironmentDialog, { enable: environments.length > 0 });
|
||||
|
||||
const items: DropdownItem[] = useMemo(
|
||||
() => [
|
||||
|
||||
@@ -3,8 +3,12 @@ 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';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
|
||||
import { useCreateRequest } from '../hooks/useCreateRequest';
|
||||
import { useRequests } from '../hooks/useRequests';
|
||||
import { clamp } from '../lib/clamp';
|
||||
import { HotKeyList } from './core/HotKeyList';
|
||||
import { RequestPane } from './RequestPane';
|
||||
import { ResizeHandle } from './ResizeHandle';
|
||||
import { ResponsePane } from './ResponsePane';
|
||||
@@ -24,6 +28,9 @@ const STACK_VERTICAL_WIDTH = 600;
|
||||
|
||||
export const RequestResponse = memo(function RequestResponse({ style }: Props) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const activeRequest = useActiveRequest();
|
||||
const createRequest = useCreateRequest();
|
||||
const requests = useRequests();
|
||||
const [vertical, setVertical] = useState<boolean>(false);
|
||||
const [widthRaw, setWidth] = useLocalStorage<number>(`body_width::${useActiveWorkspaceId()}`);
|
||||
const [heightRaw, setHeight] = useLocalStorage<number>(`body_height::${useActiveWorkspaceId()}`);
|
||||
@@ -114,6 +121,24 @@ export const RequestResponse = memo(function RequestResponse({ style }: Props) {
|
||||
[width, height, vertical, setHeight, setWidth],
|
||||
);
|
||||
|
||||
if (activeRequest === null && requests.length > 0) {
|
||||
return (
|
||||
<div className="h-full flex items-center justify-center opacity-disabled">
|
||||
<p>No Selected Request</p>
|
||||
</div>
|
||||
);
|
||||
} else if (requests.length === 0) {
|
||||
return (
|
||||
<HotKeyList
|
||||
hotkeys={[
|
||||
{ action: 'request.create', label: 'New Request' },
|
||||
{ action: 'sidebar.toggle', label: 'Toggle Sidebar' },
|
||||
{ action: 'urlBar.focus', label: 'Focus URL' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="grid gap-1.5 w-full h-full p-3" style={styles}>
|
||||
<RequestPane style={rqst} fullHeight={!vertical} />
|
||||
|
||||
@@ -2,7 +2,7 @@ import classNames from 'classnames';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { createGlobalState } from 'react-use';
|
||||
import { useActiveRequestId } from '../hooks/useActiveRequestId';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useLatestResponse } from '../hooks/useLatestResponse';
|
||||
import { useResponseContentType } from '../hooks/useResponseContentType';
|
||||
import { useResponses } from '../hooks/useResponses';
|
||||
@@ -12,6 +12,7 @@ import { isResponseLoading } from '../lib/models';
|
||||
import { Banner } from './core/Banner';
|
||||
import { CountBadge } from './core/CountBadge';
|
||||
import { DurationTag } from './core/DurationTag';
|
||||
import { HotKeyList } from './core/HotKeyList';
|
||||
import { SizeTag } from './core/SizeTag';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { StatusTag } from './core/StatusTag';
|
||||
@@ -34,9 +35,9 @@ const useActiveTab = createGlobalState<string>('body');
|
||||
|
||||
export const ResponsePane = memo(function ResponsePane({ style, className }: Props) {
|
||||
const [pinnedResponseId, setPinnedResponseId] = useState<string | null>(null);
|
||||
const activeRequestId = useActiveRequestId();
|
||||
const latestResponse = useLatestResponse(activeRequestId);
|
||||
const responses = useResponses(activeRequestId);
|
||||
const activeRequest = useActiveRequest();
|
||||
const latestResponse = useLatestResponse(activeRequest?.id ?? null);
|
||||
const responses = useResponses(activeRequest?.id ?? null);
|
||||
const activeResponse: HttpResponse | null = pinnedResponseId
|
||||
? responses.find((r) => r.id === pinnedResponseId) ?? null
|
||||
: latestResponse ?? null;
|
||||
@@ -84,6 +85,10 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
[activeResponse?.headers, contentType, setViewMode, viewMode],
|
||||
);
|
||||
|
||||
if (activeRequest === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={style}
|
||||
@@ -95,6 +100,19 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
|
||||
)}
|
||||
>
|
||||
{activeResponse?.error && <Banner className="m-2">{activeResponse.error}</Banner>}
|
||||
{!activeResponse && (
|
||||
<>
|
||||
<span />
|
||||
<HotKeyList
|
||||
hotkeys={[
|
||||
{ action: 'request.send', label: 'Send Request' },
|
||||
{ action: 'request.create', label: 'New Request' },
|
||||
{ action: 'sidebar.toggle', label: 'Toggle Sidebar' },
|
||||
{ action: 'urlBar.focus', label: 'Focus URL' },
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{activeResponse && !activeResponse.error && !isResponseLoading(activeResponse) && (
|
||||
<>
|
||||
<HStack
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { invoke, shell } from '@tauri-apps/api';
|
||||
import { useRef } from 'react';
|
||||
import { useAppVersion } from '../hooks/useAppVersion';
|
||||
import { useExportData } from '../hooks/useExportData';
|
||||
@@ -79,6 +79,12 @@ export function SettingsDropdown() {
|
||||
onSelect: () => invoke('check_for_updates'),
|
||||
leftSlot: <Icon icon="update" />,
|
||||
},
|
||||
{
|
||||
key: 'feedback',
|
||||
label: 'Feedback',
|
||||
onSelect: () => shell.open('https://yaak.canny.io'),
|
||||
leftSlot: <Icon icon="chat" />,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<IconButton size="sm" title="Request Options" icon="gear" className="pointer-events-auto" />
|
||||
|
||||
@@ -5,9 +5,11 @@ import { useOsInfo } from '../../hooks/useOsInfo';
|
||||
|
||||
interface Props {
|
||||
action: HotkeyAction | null;
|
||||
className?: string;
|
||||
variant?: 'text' | 'with-bg';
|
||||
}
|
||||
|
||||
export function HotKey({ action }: Props) {
|
||||
export function HotKey({ action, className, variant }: Props) {
|
||||
const osInfo = useOsInfo();
|
||||
const label = useFormattedHotkey(action);
|
||||
if (label === null || osInfo == null) {
|
||||
@@ -15,6 +17,14 @@ export function HotKey({ action }: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={classNames('text-sm text-gray-1000 text-opacity-disabled')}>{label}</span>
|
||||
<span
|
||||
className={classNames(
|
||||
className,
|
||||
variant === 'with-bg' && 'rounded border',
|
||||
'text-sm text-gray-1000 text-opacity-disabled',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
24
src-web/components/core/HotKeyList.tsx
Normal file
24
src-web/components/core/HotKeyList.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import type { HotkeyAction } from '../../hooks/useHotkey';
|
||||
import { HotKey } from './HotKey';
|
||||
|
||||
interface Props {
|
||||
hotkeys: { action: HotkeyAction; label: string }[];
|
||||
}
|
||||
|
||||
export const HotKeyList = ({ hotkeys }: Props) => {
|
||||
return (
|
||||
<div className="mx-auto h-full flex items-center text-gray-800 text-sm">
|
||||
<div className="flex flex-col gap-1">
|
||||
{hotkeys.map((hotkey) => (
|
||||
<div key={hotkey.action} className="grid grid-cols-2">
|
||||
<p>{hotkey.label}</p>
|
||||
<div className="ml-auto">
|
||||
<HotKey action={hotkey.action} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import { ReactComponent as LeftPanelVisibleIcon } from '../../assets/icons/LeftP
|
||||
const icons = {
|
||||
archive: ReactIcons.ArchiveIcon,
|
||||
camera: ReactIcons.CameraIcon,
|
||||
chat: ReactIcons.ChatBubbleIcon,
|
||||
check: ReactIcons.CheckIcon,
|
||||
checkbox: ReactIcons.CheckboxIcon,
|
||||
clock: ReactIcons.ClockIcon,
|
||||
|
||||
@@ -22,15 +22,26 @@ const hotkeys: Record<HotkeyAction, string[]> = {
|
||||
'environmentEditor.toggle': ['CmdCtrl+e'],
|
||||
};
|
||||
|
||||
export function useHotkey(action: HotkeyAction | null, callback: (e: KeyboardEvent) => void) {
|
||||
interface Options {
|
||||
enable?: boolean;
|
||||
}
|
||||
|
||||
export function useHotkey(
|
||||
action: HotkeyAction | null,
|
||||
callback: (e: KeyboardEvent) => void,
|
||||
options: Options = {},
|
||||
) {
|
||||
useAnyHotkey((hkAction, e) => {
|
||||
if (hkAction === action) {
|
||||
callback(e);
|
||||
}
|
||||
});
|
||||
}, options);
|
||||
}
|
||||
|
||||
export function useAnyHotkey(callback: (action: HotkeyAction, e: KeyboardEvent) => void) {
|
||||
export function useAnyHotkey(
|
||||
callback: (action: HotkeyAction, e: KeyboardEvent) => void,
|
||||
options: Options,
|
||||
) {
|
||||
const currentKeys = useRef<Set<string>>(new Set());
|
||||
const callbackRef = useRef(callback);
|
||||
const osInfo = useOsInfo();
|
||||
@@ -45,6 +56,10 @@ export function useAnyHotkey(callback: (action: HotkeyAction, e: KeyboardEvent)
|
||||
const clearCurrentKeys = debounce(() => currentKeys.current.clear(), 1000);
|
||||
|
||||
const down = (e: KeyboardEvent) => {
|
||||
if (options.enable === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentKeys.current.add(normalizeKey(e.key, os));
|
||||
|
||||
for (const [hkAction, hkKeys] of Object.entries(hotkeys) as [HotkeyAction, string[]][]) {
|
||||
@@ -64,6 +79,9 @@ export function useAnyHotkey(callback: (action: HotkeyAction, e: KeyboardEvent)
|
||||
clearCurrentKeys();
|
||||
};
|
||||
const up = (e: KeyboardEvent) => {
|
||||
if (options.enable === false) {
|
||||
return;
|
||||
}
|
||||
currentKeys.current.delete(normalizeKey(e.key, os));
|
||||
};
|
||||
window.addEventListener('keydown', down);
|
||||
|
||||
Reference in New Issue
Block a user