diff --git a/src-tauri/migrations/20250604102922_colored-methods-setting.sql b/src-tauri/migrations/20250604102922_colored-methods-setting.sql new file mode 100644 index 00000000..ca26f6a5 --- /dev/null +++ b/src-tauri/migrations/20250604102922_colored-methods-setting.sql @@ -0,0 +1,2 @@ +ALTER TABLE settings + ADD COLUMN colored_methods BOOLEAN DEFAULT FALSE; diff --git a/src-tauri/yaak-models/bindings/gen_models.ts b/src-tauri/yaak-models/bindings/gen_models.ts index ba28db69..cd0906f1 100644 --- a/src-tauri/yaak-models/bindings/gen_models.ts +++ b/src-tauri/yaak-models/bindings/gen_models.ts @@ -62,7 +62,7 @@ export type ProxySetting = { "type": "enabled", disabled: boolean, http: string, export type ProxySettingAuth = { user: string, password: string, }; -export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, editorKeymap: EditorKeymap, }; +export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, editorKeymap: EditorKeymap, coloredMethods: boolean, }; export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, }; diff --git a/src-tauri/yaak-models/bindings/gen_util.ts b/src-tauri/yaak-models/bindings/gen_util.ts index a38424de..482d1902 100644 --- a/src-tauri/yaak-models/bindings/gen_util.ts +++ b/src-tauri/yaak-models/bindings/gen_util.ts @@ -1,9 +1,9 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Environment } from "./gen_models"; -import type { Folder } from "./gen_models"; -import type { GrpcRequest } from "./gen_models"; -import type { HttpRequest } from "./gen_models"; -import type { WebsocketRequest } from "./gen_models"; -import type { Workspace } from "./gen_models"; +import type { Environment } from "./gen_models.js"; +import type { Folder } from "./gen_models.js"; +import type { GrpcRequest } from "./gen_models.js"; +import type { HttpRequest } from "./gen_models.js"; +import type { WebsocketRequest } from "./gen_models.js"; +import type { Workspace } from "./gen_models.js"; export type BatchUpsertResult = { workspaces: Array, environments: Array, folders: Array, httpRequests: Array, grpcRequests: Array, websocketRequests: Array, }; diff --git a/src-tauri/yaak-models/src/models.rs b/src-tauri/yaak-models/src/models.rs index 57e94546..323aae14 100644 --- a/src-tauri/yaak-models/src/models.rs +++ b/src-tauri/yaak-models/src/models.rs @@ -114,6 +114,7 @@ pub struct Settings { pub theme_light: String, pub update_channel: String, pub editor_keymap: EditorKeymap, + pub colored_methods: bool, } impl UpsertModelInfo for Settings { @@ -160,6 +161,7 @@ impl UpsertModelInfo for Settings { (ThemeDark, self.theme_dark.as_str().into()), (ThemeLight, self.theme_light.as_str().into()), (UpdateChannel, self.update_channel.into()), + (ColoredMethods, self.colored_methods.into()), (Proxy, proxy.into()), ]) } @@ -179,6 +181,7 @@ impl UpsertModelInfo for Settings { SettingsIden::ThemeDark, SettingsIden::ThemeLight, SettingsIden::UpdateChannel, + SettingsIden::ColoredMethods, ] } @@ -205,6 +208,7 @@ impl UpsertModelInfo for Settings { theme_light: row.get("theme_light")?, hide_window_controls: row.get("hide_window_controls")?, update_channel: row.get("update_channel")?, + colored_methods: row.get("colored_methods")?, }) } } diff --git a/src-tauri/yaak-models/src/queries/settings.rs b/src-tauri/yaak-models/src/queries/settings.rs index 5635abd5..e5090e52 100644 --- a/src-tauri/yaak-models/src/queries/settings.rs +++ b/src-tauri/yaak-models/src/queries/settings.rs @@ -29,6 +29,7 @@ impl<'a> DbContext<'a> { theme_dark: "yaak-dark".to_string(), theme_light: "yaak-light".to_string(), update_channel: "stable".to_string(), + colored_methods: false, }; self.upsert(&settings, &UpdateSource::Background).expect("Failed to upsert settings") } diff --git a/src-web/components/CommandPaletteDialog.tsx b/src-web/components/CommandPaletteDialog.tsx index 1010a045..6bb9d126 100644 --- a/src-web/components/CommandPaletteDialog.tsx +++ b/src-web/components/CommandPaletteDialog.tsx @@ -236,7 +236,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) { return workspaces; } - const r = [...workspaces].sort((a, b) => { + return [...workspaces].sort((a, b) => { const aRecentIndex = recentWorkspaces?.indexOf(a.id); const bRecentIndex = recentWorkspaces?.indexOf(b.id); @@ -250,7 +250,6 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) { return a.createdAt.localeCompare(b.createdAt); } }); - return r; }, [recentWorkspaces, workspaces]); const groups = useMemo(() => { @@ -272,7 +271,7 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) { searchText: resolvedModelNameWithFolders(r), label: ( - +
{resolvedModelNameWithFolders(r)}
), diff --git a/src-web/components/HttpRequestPane.tsx b/src-web/components/HttpRequestPane.tsx index 4b1551f5..9458ef28 100644 --- a/src-web/components/HttpRequestPane.tsx +++ b/src-web/components/HttpRequestPane.tsx @@ -41,8 +41,7 @@ import type { GenericCompletionConfig } from './core/Editor/genericCompletion'; import { InlineCode } from './core/InlineCode'; import type { Pair } from './core/PairEditor'; import { PlainInput } from './core/PlainInput'; -import type { TabItem } from './core/Tabs/Tabs'; -import { TabContent, Tabs } from './core/Tabs/Tabs'; +import { TabContent, TabItem, Tabs } from './core/Tabs/Tabs'; import { EmptyStateText } from './EmptyStateText'; import { FormMultipartEditor } from './FormMultipartEditor'; import { FormUrlencodedEditor } from './FormUrlencodedEditor'; @@ -50,6 +49,7 @@ import { GraphQLEditor } from './GraphQLEditor'; import { HeadersEditor } from './HeadersEditor'; import { HttpAuthenticationEditor } from './HttpAuthenticationEditor'; import { MarkdownEditor } from './MarkdownEditor'; +import { RequestMethodDropdown } from './RequestMethodDropdown'; import { UrlBar } from './UrlBar'; import { UrlParametersEditor } from './UrlParameterEditor'; @@ -138,10 +138,9 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }: activeRequest.bodyType === BODY_TYPE_FORM_URLENCODED || activeRequest.bodyType === BODY_TYPE_FORM_MULTIPART ) { - const n = Array.isArray(activeRequest.body?.form) + numParams = Array.isArray(activeRequest.body?.form) ? activeRequest.body.form.filter((p) => p.name).length : 0; - numParams = n; } const tabs = useMemo( @@ -314,11 +313,6 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }: [activeRequest.id, sendRequest], ); - const handleMethodChange = useCallback( - (method: string) => patchModel(activeRequest, { method }), - [activeRequest], - ); - const handleUrlChange = useCallback( (url: string) => patchModel(activeRequest, { url }), [activeRequest], @@ -335,14 +329,17 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }: stateKey={`url.${activeRequest.id}`} key={forceUpdateKey + urlKey} url={activeRequest.url} - method={activeRequest.method} placeholder="https://example.com" onPasteOverwrite={handlePaste} autocomplete={autocomplete} onSend={handleSend} onCancel={cancelResponse} - onMethodChange={handleMethodChange} onUrlChange={handleUrlChange} + leftSlot={ +
+ +
+ } forceUpdateKey={updateKey} isLoading={activeResponse != null && activeResponse.state !== 'closed'} /> diff --git a/src-web/components/RecentHttpResponsesDropdown.tsx b/src-web/components/RecentHttpResponsesDropdown.tsx index ec10ed80..00baff39 100644 --- a/src-web/components/RecentHttpResponsesDropdown.tsx +++ b/src-web/components/RecentHttpResponsesDropdown.tsx @@ -67,7 +67,7 @@ export const RecentHttpResponsesDropdown = function ResponsePane({ ...responses.map((r: HttpResponse) => ({ label: ( - + {' '} {r.elapsed >= 0 ? `${r.elapsed}ms` : 'n/a'} diff --git a/src-web/components/RecentRequestsDropdown.tsx b/src-web/components/RecentRequestsDropdown.tsx index 5e005c17..1046eda7 100644 --- a/src-web/components/RecentRequestsDropdown.tsx +++ b/src-web/components/RecentRequestsDropdown.tsx @@ -58,7 +58,7 @@ export function RecentRequestsDropdown({ className }: Props) { recentRequestItems.push({ label: resolvedModelName(request), - leftSlot: , + leftSlot: , onSelect: async () => { await router.navigate({ to: '/workspaces/$workspaceId', diff --git a/src-web/components/RequestMethodDropdown.tsx b/src-web/components/RequestMethodDropdown.tsx index 39048447..e0e7fce0 100644 --- a/src-web/components/RequestMethodDropdown.tsx +++ b/src-web/components/RequestMethodDropdown.tsx @@ -1,16 +1,17 @@ +import { HttpRequest, patchModel } from '@yaakapp-internal/models'; import classNames from 'classnames'; -import { memo, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { showPrompt } from '../lib/prompt'; import { Button } from './core/Button'; import type { DropdownItem } from './core/Dropdown'; +import { HttpMethodTag } from './core/HttpMethodTag'; import { Icon } from './core/Icon'; import type { RadioDropdownItem } from './core/RadioDropdown'; import { RadioDropdown } from './core/RadioDropdown'; type Props = { - method: string; + request: HttpRequest; className?: string; - onChange: (method: string) => void; }; const radioItems: RadioDropdownItem[] = [ @@ -28,10 +29,13 @@ const radioItems: RadioDropdownItem[] = [ })); export const RequestMethodDropdown = memo(function RequestMethodDropdown({ - method, - onChange, + request, className, }: Props) { + const handleChange = useCallback(async (method: string) => { + await patchModel(request, { method }); + }, []); + const itemsAfter = useMemo( () => [ { @@ -49,17 +53,22 @@ export const RequestMethodDropdown = memo(function RequestMethodDropdown({ placeholder: 'CUSTOM', }); if (newMethod == null) return; - onChange(newMethod); + await handleChange(newMethod); }, }, ], - [onChange], + [], ); return ( - + ); diff --git a/src-web/components/Settings/SettingsAppearance.tsx b/src-web/components/Settings/SettingsAppearance.tsx index 9fd37a07..c1584a56 100644 --- a/src-web/components/Settings/SettingsAppearance.tsx +++ b/src-web/components/Settings/SettingsAppearance.tsx @@ -122,6 +122,11 @@ export function SettingsAppearance() { title="Wrap Editor Lines" onChange={(editorSoftWrap) => patchModel(settings, { editorSoftWrap })} /> + patchModel(settings, { coloredMethods })} + /> {type() !== 'macos' && ( & { className?: string; - method: HttpRequest['method'] | null; placeholder: string; onSend: () => void; onUrlChange: (url: string) => void; @@ -21,10 +19,10 @@ type Props = Pick & { onPasteOverwrite?: InputProps['onPasteOverwrite']; onCancel: () => void; submitIcon?: IconProps['icon'] | null; - onMethodChange?: (method: string) => void; isLoading: boolean; forceUpdateKey: string; rightSlot?: ReactNode; + leftSlot?: ReactNode; autocomplete?: InputProps['autocomplete']; stateKey: InputProps['stateKey']; }; @@ -33,16 +31,15 @@ export const UrlBar = memo(function UrlBar({ forceUpdateKey, onUrlChange, url, - method, placeholder, className, onSend, onCancel, - onMethodChange, onPaste, onPasteOverwrite, submitIcon = 'send_horizontal', autocomplete, + leftSlot, rightSlot, isLoading, stateKey, @@ -87,18 +84,7 @@ export const UrlBar = memo(function UrlBar({ onChange={onUrlChange} defaultValue={url} placeholder={placeholder} - leftSlot={ - method != null && - onMethodChange != null && ( -
- -
- ) - } + leftSlot={leftSlot} rightSlot={ {rightSlot &&
{rightSlot}
} diff --git a/src-web/components/core/Dropdown.tsx b/src-web/components/core/Dropdown.tsx index 30ad2748..93d9d6bf 100644 --- a/src-web/components/core/Dropdown.tsx +++ b/src-web/components/core/Dropdown.tsx @@ -642,7 +642,7 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men justify="start" leftSlot={ (isLoading || item.leftSlot) && ( -
+
{isLoading ? : item.leftSlot}
) diff --git a/src-web/components/core/HttpMethodTag.tsx b/src-web/components/core/HttpMethodTag.tsx index f2f5bb21..abb21ea5 100644 --- a/src-web/components/core/HttpMethodTag.tsx +++ b/src-web/components/core/HttpMethodTag.tsx @@ -1,10 +1,11 @@ -import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models'; +import { GrpcRequest, HttpRequest, settingsAtom, WebsocketRequest } from '@yaakapp-internal/models'; import classNames from 'classnames'; +import { useAtomValue } from 'jotai'; interface Props { request: HttpRequest | GrpcRequest | WebsocketRequest; className?: string; - shortNames?: boolean; + short?: boolean; } const methodNames: Record = { @@ -18,7 +19,8 @@ const methodNames: Record = { query: 'QURY', }; -export function HttpMethodTag({ request, className }: Props) { +export function HttpMethodTag({ request, className, short }: Props) { + const settings = useAtomValue(settingsAtom); const method = request.model === 'http_request' && request.bodyType === 'graphql' ? 'GQL' @@ -26,19 +28,34 @@ export function HttpMethodTag({ request, className }: Props) { ? 'GRPC' : request.model === 'websocket_request' ? 'WS' - : (methodNames[request.method.toLowerCase()] ?? request.method.slice(0, 4)); + : request.method; + let label = method.toUpperCase(); - const paddedMethod = method.padStart(4, ' ').toUpperCase(); + if (short) { + label = methodNames[method.toLowerCase()] ?? method.slice(0, 4); + label = label.padStart(4, ' '); + } return ( - {paddedMethod} + {label} ); } diff --git a/src-web/components/core/HttpStatusTag.tsx b/src-web/components/core/HttpStatusTag.tsx index 715cda2f..b94560e4 100644 --- a/src-web/components/core/HttpStatusTag.tsx +++ b/src-web/components/core/HttpStatusTag.tsx @@ -5,19 +5,20 @@ interface Props { response: HttpResponse; className?: string; showReason?: boolean; + short?: boolean; } -export function HttpStatusTag({ response, className, showReason }: Props) { +export function HttpStatusTag({ response, className, showReason, short }: Props) { const { status, state } = response; let colorClass; let label = `${status}`; if (state === 'initialized') { - label = 'CONNECTING'; + label = short ? 'CONN' : 'CONNECTING'; colorClass = 'text-text-subtle'; } else if (status < 100) { - label = 'ERROR'; + label = short ? 'ERR' : 'ERROR'; colorClass = 'text-danger'; } else if (status < 200) { colorClass = 'text-info'; @@ -33,8 +34,7 @@ export function HttpStatusTag({ response, className, showReason }: Props) { return ( - {label}{' '} - {showReason && 'statusReason' in response ? response.statusReason : null} + {label} {showReason && 'statusReason' in response ? response.statusReason : null} ); } diff --git a/src-web/components/core/Input.tsx b/src-web/components/core/Input.tsx index b87e5eab..bcf6903f 100644 --- a/src-web/components/core/Input.tsx +++ b/src-web/components/core/Input.tsx @@ -53,11 +53,11 @@ export type InputProps = Pick< > & { className?: string; containerClassName?: string; + inputWrapperClassName?: string; defaultValue?: string | null; disableObscureToggle?: boolean; fullHeight?: boolean; hideLabel?: boolean; - inputWrapperClassName?: string; help?: ReactNode; label: ReactNode; labelClassName?: string; diff --git a/src-web/components/sidebar/SidebarItem.tsx b/src-web/components/sidebar/SidebarItem.tsx index de5d73ee..8a31de37 100644 --- a/src-web/components/sidebar/SidebarItem.tsx +++ b/src-web/components/sidebar/SidebarItem.tsx @@ -12,7 +12,7 @@ import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from ' import type { XYCoord } from 'react-dnd'; import { useDrag, useDrop } from 'react-dnd'; import { activeRequestAtom } from '../../hooks/useActiveRequest'; -import {allRequestsAtom} from "../../hooks/useAllRequests"; +import { allRequestsAtom } from '../../hooks/useAllRequests'; import { useScrollIntoView } from '../../hooks/useScrollIntoView'; import { useSidebarItemCollapsed } from '../../hooks/useSidebarItemCollapsed'; import { jotaiStore } from '../../lib/jotai'; @@ -214,10 +214,13 @@ export const SidebarItem = memo(function SidebarItem({ return null; } + const opacitySubtle = 'opacity-80'; + const itemPrefix = item.model !== 'folder' && ( ); @@ -287,7 +290,11 @@ export const SidebarItem = memo(function SidebarItem({ {latestHttpResponse.state !== 'closed' ? ( ) : ( - + )}
) : null}