Show hotkeys on empty views

This commit is contained in:
Gregory Schier
2024-01-08 15:13:44 -08:00
parent 88ea68e72f
commit 793bff9f27
8 changed files with 113 additions and 11 deletions

View File

@@ -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(
() => [

View File

@@ -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} />

View File

@@ -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

View File

@@ -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" />

View File

@@ -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>
);
}

View 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>
);
};

View File

@@ -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,

View File

@@ -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);