Hot keys and cleanup

This commit is contained in:
Gregory Schier
2023-04-03 07:59:49 -07:00
parent bc0e86757c
commit 3128e9ce76
11 changed files with 149 additions and 129 deletions

View File

@@ -5,19 +5,22 @@ on:
jobs: jobs:
build-artifacts: build-artifacts:
runs-on: ${{ matrix.platform }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
# platform: [ ubuntu-latest, macos-latest, windows-latest ] include:
platform: [ macos-latest ] - os: macos-latest
target: aarch64-apple-darwin
- os: macos-latest
target: x86_64-apple-darwin
runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with: with:
targets: 'aarch64-apple-darwin,x86_64-apple-darwin' targets: ${{ matrix.target }}
- name: Cache Rust - name: Cache Rust
uses: actions/cache@v2 uses: actions/cache@v2
with: with:
@@ -31,7 +34,7 @@ jobs:
node-version: 18 node-version: 18
cache: 'npm' cache: 'npm'
- name: install dependencies (ubuntu only) - name: install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
@@ -55,7 +58,7 @@ jobs:
with: with:
tagName: 'v__VERSION__' tagName: 'v__VERSION__'
releaseName: 'Release __VERSION__' releaseName: 'Release __VERSION__'
releaseBody: 'See the assets to download this version and install.' releaseBody: '<!-- Release Notes -->'
releaseDraft: false releaseDraft: true
prerelease: false prerelease: false
args: '--target universal-apple-darwin' args: '--target ${{ matrix.target }}'

View File

@@ -79,11 +79,8 @@ async fn actually_send_ephemeral_request(
let start = std::time::Instant::now(); let start = std::time::Instant::now();
let mut url_string = request.url.to_string(); let mut url_string = request.url.to_string();
let mut variables = HashMap::new(); let variables: HashMap<&str, &str> = HashMap::new();
variables.insert("PROJECT_ID", "project_123"); // variables.insert("", "");
variables.insert("TOKEN", "s3cret");
variables.insert("DOMAIN", "schier.co");
variables.insert("BASE_URL", "https://schier.co");
let re = Regex::new(r"\$\{\[\s*([^]\s]+)\s*]}").expect("Failed to create regex"); let re = Regex::new(r"\$\{\[\s*([^]\s]+)\s*]}").expect("Failed to create regex");
url_string = re url_string = re
@@ -608,6 +605,14 @@ fn create_window(handle: &AppHandle<Wry>) -> Window<Wry> {
.add_item( .add_item(
CustomMenuItem::new("focus_url".to_string(), "Focus URL").accelerator("CmdOrCtrl+l"), CustomMenuItem::new("focus_url".to_string(), "Focus URL").accelerator("CmdOrCtrl+l"),
) )
.add_item(
CustomMenuItem::new("new_request".to_string(), "New Request")
.accelerator("CmdOrCtrl+n"),
)
.add_item(
CustomMenuItem::new("duplicate_request".to_string(), "Duplicate Request")
.accelerator("CmdOrCtrl+d"),
)
.add_item(CustomMenuItem::new("new_window".to_string(), "New Window")); .add_item(CustomMenuItem::new("new_window".to_string(), "New Window"));
if is_dev() { if is_dev() {
test_menu = test_menu test_menu = test_menu
@@ -652,6 +657,8 @@ fn create_window(handle: &AppHandle<Wry>) -> Window<Wry> {
"toggle_sidebar" => win2.emit("toggle_sidebar", true).unwrap(), "toggle_sidebar" => win2.emit("toggle_sidebar", true).unwrap(),
"focus_url" => win2.emit("focus_url", true).unwrap(), "focus_url" => win2.emit("focus_url", true).unwrap(),
"send_request" => win2.emit("send_request", true).unwrap(), "send_request" => win2.emit("send_request", true).unwrap(),
"new_request" => _ = win2.emit("new_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),
"toggle_devtools" => { "toggle_devtools" => {

View File

@@ -8,7 +8,7 @@
}, },
"package": { "package": {
"productName": "Yaak", "productName": "Yaak",
"version": "2023.0.10" "version": "2023.0.11"
}, },
"tauri": { "tauri": {
"windows": [], "windows": [],

View File

@@ -4,6 +4,7 @@ import { useDeleteRequest } from '../hooks/useDeleteRequest';
import { useDuplicateRequest } from '../hooks/useDuplicateRequest'; import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
import { useRequest } from '../hooks/useRequest'; import { useRequest } from '../hooks/useRequest';
import { Dropdown } from './core/Dropdown'; import { Dropdown } from './core/Dropdown';
import { HotKey } from './core/HotKey';
import { Icon } from './core/Icon'; import { Icon } from './core/Icon';
import { InlineCode } from './core/InlineCode'; import { InlineCode } from './core/InlineCode';
@@ -25,6 +26,7 @@ export function RequestActionsDropdown({ requestId, children }: Props) {
label: 'Duplicate', label: 'Duplicate',
onSelect: duplicateRequest.mutate, onSelect: duplicateRequest.mutate,
leftSlot: <Icon icon="copy" />, leftSlot: <Icon icon="copy" />,
rightSlot: <HotKey>D</HotKey>,
}, },
{ {
label: 'Delete', label: 'Delete',

View File

@@ -80,105 +80,109 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
'shadow shadow-gray-100 dark:shadow-gray-0 relative', 'shadow shadow-gray-100 dark:shadow-gray-0 relative',
)} )}
> >
<HStack {activeResponse && (
alignItems="center" <>
className={classnames( <HStack
'italic text-gray-700 text-sm w-full flex-shrink-0', alignItems="center"
// Remove a bit of space because the tabs have lots too className={classnames(
'-mb-1.5', 'italic text-gray-700 text-sm w-full flex-shrink-0',
)} // Remove a bit of space because the tabs have lots too
> '-mb-1.5',
{activeResponse && ( )}
<> >
<div className="whitespace-nowrap p-3 py-2"> {activeResponse && (
<StatusColor statusCode={activeResponse.status}> <>
{activeResponse.status} <div className="whitespace-nowrap p-3 py-2">
{activeResponse.statusReason && ` ${activeResponse.statusReason}`} <StatusColor statusCode={activeResponse.status}>
</StatusColor> {activeResponse.status}
&nbsp;&bull;&nbsp; {activeResponse.statusReason && ` ${activeResponse.statusReason}`}
{activeResponse.elapsed}ms &nbsp;&bull;&nbsp; </StatusColor>
{Math.round(activeResponse.body.length / 1000)} KB &nbsp;&bull;&nbsp;
</div> {activeResponse.elapsed}ms &nbsp;&bull;&nbsp;
{Math.round(activeResponse.body.length / 1000)} KB
</div>
<Dropdown <Dropdown
items={[ items={[
{ {
label: viewMode === 'pretty' ? 'View Raw' : 'View Prettified', label: viewMode === 'pretty' ? 'View Raw' : 'View Prettified',
onSelect: toggleViewMode, onSelect: toggleViewMode,
}, },
{ type: 'separator', label: 'Actions' }, { type: 'separator', label: 'Actions' },
{ {
label: 'Clear Response', label: 'Clear Response',
onSelect: deleteResponse.mutate, onSelect: deleteResponse.mutate,
disabled: responses.length === 0, disabled: responses.length === 0,
}, },
{ {
label: `Clear ${responses.length} ${pluralize('Response', responses.length)}`, label: `Clear ${responses.length} ${pluralize('Response', responses.length)}`,
onSelect: deleteAllResponses.mutate, onSelect: deleteAllResponses.mutate,
hidden: responses.length <= 1, hidden: responses.length <= 1,
disabled: responses.length === 0, disabled: responses.length === 0,
}, },
{ type: 'separator', label: 'History' }, { type: 'separator', label: 'History' },
...responses.slice(0, 10).map((r) => ({ ...responses.slice(0, 10).map((r) => ({
label: r.status + ' - ' + r.elapsed + ' ms', label: r.status + ' - ' + r.elapsed + ' ms',
leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <></>, leftSlot: activeResponse?.id === r.id ? <Icon icon="check" /> : <></>,
onSelect: () => setPinnedResponseId(r.id), onSelect: () => setPinnedResponseId(r.id),
})), })),
]} ]}
>
<IconButton
title="Show response history"
icon="triangleDown"
className="ml-auto"
size="sm"
iconSize="md"
/>
</Dropdown>
</>
)}
</HStack>
{activeResponse?.error ? (
<Banner className="m-2">{activeResponse.error}</Banner>
) : (
<Tabs
value={activeTab}
onChangeValue={setActiveTab}
label="Response"
className="px-3"
tabs={tabs}
> >
<IconButton <TabContent value="body">
title="Show response history" {!activeResponse.body ? (
icon="triangleDown" <EmptyStateText>No Response</EmptyStateText>
className="ml-auto" ) : viewMode === 'pretty' && contentType.includes('html') ? (
size="sm" <Webview
iconSize="md" body={activeResponse.body}
/> contentType={contentType}
</Dropdown> url={activeResponse.url}
</> />
)} ) : viewMode === 'pretty' && contentType.includes('json') ? (
</HStack> <Editor
readOnly
{activeResponse?.error ? ( forceUpdateKey={`pretty::${activeResponse.updatedAt}`}
<Banner className="m-2">{activeResponse.error}</Banner> className="bg-gray-50 dark:!bg-gray-100"
) : ( defaultValue={tryFormatJson(activeResponse?.body)}
<Tabs contentType={contentType}
value={activeTab} />
onChangeValue={setActiveTab} ) : activeResponse?.body ? (
label="Response" <Editor
className="px-3" readOnly
tabs={tabs} forceUpdateKey={activeResponse.updatedAt}
> className="bg-gray-50 dark:!bg-gray-100"
<TabContent value="body"> defaultValue={activeResponse?.body}
{activeResponse === null ? ( contentType={contentType}
<EmptyStateText>No Response</EmptyStateText> />
) : viewMode === 'pretty' && contentType.includes('html') ? ( ) : null}
<Webview </TabContent>
body={activeResponse.body} <TabContent value="headers">
contentType={contentType} <ResponseHeaders headers={activeResponse?.headers ?? []} />
url={activeResponse.url} </TabContent>
/> </Tabs>
) : viewMode === 'pretty' && contentType.includes('json') ? ( )}
<Editor </>
readOnly
forceUpdateKey={`pretty::${activeResponse.updatedAt}`}
className="bg-gray-50 dark:!bg-gray-100"
defaultValue={tryFormatJson(activeResponse?.body)}
contentType={contentType}
/>
) : activeResponse?.body ? (
<Editor
readOnly
forceUpdateKey={activeResponse.updatedAt}
className="bg-gray-50 dark:!bg-gray-100"
defaultValue={activeResponse?.body}
contentType={contentType}
/>
) : null}
</TabContent>
<TabContent value="headers">
<ResponseHeaders headers={activeResponse?.headers ?? []} />
</TabContent>
</Tabs>
)} )}
</div> </div>
); );

View File

@@ -1,14 +1,26 @@
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useActiveRequestId } from '../hooks/useActiveRequestId';
import { useCreateRequest } from '../hooks/useCreateRequest'; import { useCreateRequest } from '../hooks/useCreateRequest';
import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { useTauriEvent } from '../hooks/useTauriEvent';
import { IconButton } from './core/IconButton'; import { IconButton } from './core/IconButton';
export const SidebarActions = memo(function SidebarDisplayToggle() { export const SidebarActions = memo(function SidebarDisplayToggle() {
const { hidden, toggle } = useSidebarHidden(); const { hidden, toggle } = useSidebarHidden();
const activeRequestId = useActiveRequestId();
const createRequest = useCreateRequest({ navigateAfter: true }); const createRequest = useCreateRequest({ navigateAfter: true });
const duplicateRequest = useDuplicateRequest({ id: activeRequestId, navigateAfter: true });
const handleCreateRequest = useCallback(() => { const handleCreateRequest = useCallback(() => {
createRequest.mutate({ name: 'New Request' }); createRequest.mutate({});
}, [createRequest]); }, [createRequest]);
useTauriEvent('new_request', () => {
createRequest.mutate({});
});
// TODO: Put this somewhere better
useTauriEvent('duplicate_request', () => {
duplicateRequest.mutate();
});
return ( return (
<> <>

View File

@@ -13,6 +13,7 @@ interface Props {
export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Props) { export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Props) {
const activeRequest = useActiveRequest(); const activeRequest = useActiveRequest();
return ( return (
<HStack <HStack
justifyContent="center" justifyContent="center"

View File

@@ -266,7 +266,7 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
onClick={handleClick} onClick={handleClick}
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 pr-4', 'min-w-[8rem] outline-none px-2 mx-1.5 h-7 flex items-center text-sm text-gray-700 whitespace-nowrap',
'focus:bg-highlight focus:text-gray-900 rounded', 'focus:bg-highlight focus:text-gray-900 rounded',
)} )}
{...props} {...props}

View File

@@ -3,18 +3,8 @@ import type { CompletionContext } from '@codemirror/autocomplete';
const openTag = '${[ '; const openTag = '${[ ';
const closeTag = ' ]}'; const closeTag = ' ]}';
const variables = [ const variables: { name: string }[] = [
{ name: 'DOMAIN' }, // TODO: Put variables here
{ name: 'BASE_URL' },
{ name: 'CONTENT_THINGY' },
{ name: 'TOKEN' },
{ name: 'PROJECT_ID' },
{ name: 'DUMMY' },
{ name: 'DUMMY_2' },
{ name: 'STRIPE_PUB_KEY' },
{ name: 'RAILWAY_TOKEN' },
{ name: 'SECRET' },
{ name: 'PORT' },
]; ];
const MIN_MATCH_VAR = 2; const MIN_MATCH_VAR = 2;

View File

@@ -5,7 +5,7 @@ export function HotKey({ children }: HTMLAttributes<HTMLSpanElement>) {
return ( return (
<span <span
className={classnames( className={classnames(
'bg-gray-400 bg-opacity-20 px-1.5 py-0.5 rounded text-sm', 'bg-highlightSecondary bg-opacity-20 px-1.5 py-0.5 rounded text-sm',
'font-mono text-gray-500 tracking-widest', 'font-mono text-gray-500 tracking-widest',
)} )}
> >

View File

@@ -16,8 +16,9 @@ export function useCreateRequest({ navigateAfter }: { navigateAfter: boolean })
if (workspaceId === null) { if (workspaceId === null) {
throw new Error("Cannot create request when there's no active workspace"); throw new Error("Cannot create request when there's no active workspace");
} }
const sortPriority = maxSortPriority(requests) + 1000; patch.name = patch.name || 'New Request';
return invoke('create_request', { sortPriority, workspaceId, ...patch }); patch.sortPriority = patch.sortPriority || maxSortPriority(requests) + 1000;
return invoke('create_request', { workspaceId, ...patch });
}, },
onSuccess: async (request) => { onSuccess: async (request) => {
queryClient.setQueryData<HttpRequest[]>( queryClient.setQueryData<HttpRequest[]>(