snake_case icons and better toast styles

This commit is contained in:
Gregory Schier
2024-09-20 07:30:11 -07:00
parent a18271d306
commit 93633875ac
55 changed files with 309 additions and 262 deletions

View File

@@ -46,7 +46,7 @@
"@tauri-apps/plugin-log": "^2.0.0-rc.1", "@tauri-apps/plugin-log": "^2.0.0-rc.1",
"@tauri-apps/plugin-os": "^2.0.0-rc.1", "@tauri-apps/plugin-os": "^2.0.0-rc.1",
"@tauri-apps/plugin-shell": "^2.0.0-rc.1", "@tauri-apps/plugin-shell": "^2.0.0-rc.1",
"@yaakapp/api": "^0.2.3", "@yaakapp/api": "^0.2.4",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"cm6-graphql": "^0.0.9", "cm6-graphql": "^0.0.9",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@yaakapp/api", "name": "@yaakapp/api",
"version": "0.2.3", "version": "0.2.5",
"main": "lib/index.js", "main": "lib/index.js",
"typings": "./lib/index.d.ts", "typings": "./lib/index.d.ts",
"files": [ "files": [

View File

@@ -26,6 +26,8 @@ export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunc
export type CallTemplateFunctionResponse = { value: string | null, }; export type CallTemplateFunctionResponse = { value: string | null, };
export type Color = "custom" | "default" | "primary" | "secondary" | "info" | "success" | "notice" | "warning" | "danger";
export type CopyTextRequest = { text: string, }; export type CopyTextRequest = { text: string, };
export type ExportHttpRequestRequest = { httpRequest: HttpRequest, }; export type ExportHttpRequestRequest = { httpRequest: HttpRequest, };
@@ -52,6 +54,8 @@ export type GetTemplateFunctionsResponse = { functions: Array<TemplateFunction>,
export type HttpRequestAction = { key: string, label: string, icon: string | null, }; export type HttpRequestAction = { key: string, label: string, icon: string | null, };
export type Icon = "copy" | "info" | "check_circle" | "alert_triangle";
export type ImportRequest = { content: string, }; export type ImportRequest = { content: string, };
export type ImportResources = { workspaces: Array<Workspace>, environments: Array<Environment>, folders: Array<Folder>, httpRequests: Array<HttpRequest>, grpcRequests: Array<GrpcRequest>, }; export type ImportResources = { workspaces: Array<Workspace>, environments: Array<Environment>, folders: Array<Folder>, httpRequests: Array<HttpRequest>, grpcRequests: Array<GrpcRequest>, };
@@ -74,7 +78,7 @@ export type SendHttpRequestRequest = { httpRequest: HttpRequest, };
export type SendHttpRequestResponse = { httpResponse: HttpResponse, }; export type SendHttpRequestResponse = { httpResponse: HttpResponse, };
export type ShowToastRequest = { message: string, variant: ToastVariant, }; export type ShowToastRequest = { message: string, color?: Color | null, icon?: Icon | null, };
export type TemplateFunction = { name: string, args: Array<TemplateFunctionArg>, }; export type TemplateFunction = { name: string, args: Array<TemplateFunctionArg>, };
@@ -91,5 +95,3 @@ export type TemplateFunctionSelectArg = { options: Array<TemplateFunctionSelectO
export type TemplateFunctionSelectOption = { name: string, value: string, }; export type TemplateFunctionSelectOption = { name: string, value: string, };
export type TemplateFunctionTextArg = { placeholder?: string | null, name: string, optional?: boolean | null, label?: string | null, defaultValue?: string | null, }; export type TemplateFunctionTextArg = { placeholder?: string | null, name: string, optional?: boolean | null, label?: string | null, defaultValue?: string | null, };
export type ToastVariant = "custom" | "copied" | "success" | "info" | "warning" | "error";

View File

@@ -1,5 +1,5 @@
export type * from './plugins'; export type * from './plugins';
export type * from './themes'; export type * from './themes';
export * from './gen/models'; export * from './gen/model_util';
export * from './gen/events'; export * from './gen/events';

View File

@@ -1,12 +1,14 @@
import { FindHttpResponsesRequest } from '../gen/FindHttpResponsesRequest'; import {
import { FindHttpResponsesResponse } from '../gen/FindHttpResponsesResponse'; FindHttpResponsesRequest,
import { GetHttpRequestByIdRequest } from '../gen/GetHttpRequestByIdRequest'; FindHttpResponsesResponse,
import { GetHttpRequestByIdResponse } from '../gen/GetHttpRequestByIdResponse'; GetHttpRequestByIdRequest,
import { RenderHttpRequestRequest } from '../gen/RenderHttpRequestRequest'; GetHttpRequestByIdResponse,
import { RenderHttpRequestResponse } from '../gen/RenderHttpRequestResponse'; RenderHttpRequestRequest,
import { SendHttpRequestRequest } from '../gen/SendHttpRequestRequest'; RenderHttpRequestResponse,
import { SendHttpRequestResponse } from '../gen/SendHttpRequestResponse'; SendHttpRequestRequest,
import { ShowToastRequest } from '../gen/ShowToastRequest'; SendHttpRequestResponse,
ShowToastRequest,
} from '../gen/events';
export type Context = { export type Context = {
clipboard: { clipboard: {

View File

@@ -1,5 +1,4 @@
import { CallHttpRequestActionArgs } from '../gen/CallHttpRequestActionArgs'; import { CallHttpRequestActionArgs, HttpRequestAction } from '../gen/events';
import { HttpRequestAction } from '../gen/HttpRequestAction';
import { Context } from './Context'; import { Context } from './Context';
export type HttpRequestActionPlugin = HttpRequestAction & { export type HttpRequestActionPlugin = HttpRequestAction & {

View File

@@ -1,7 +1,4 @@
import { Environment } from '../gen/Environment'; import { Environment, Folder, HttpRequest, Workspace } from '../gen/model_util';
import { Folder } from '../gen/Folder';
import { HttpRequest } from '../gen/HttpRequest';
import { Workspace } from '../gen/Workspace';
import { AtLeast } from '../helpers'; import { AtLeast } from '../helpers';
import { Context } from './Context'; import { Context } from './Context';

View File

@@ -1,5 +1,4 @@
import { CallTemplateFunctionArgs } from '../gen/CallTemplateFunctionArgs'; import { CallTemplateFunctionArgs, TemplateFunction } from '../gen/events';
import { TemplateFunction } from '../gen/TemplateFunction';
import { Context } from './Context'; import { Context } from './Context';
export type TemplateFunctionPlugin = TemplateFunction & { export type TemplateFunctionPlugin = TemplateFunction & {

Binary file not shown.

View File

@@ -10,8 +10,8 @@ use std::process::exit;
use std::str::FromStr; use std::str::FromStr;
use std::time::Duration; use std::time::Duration;
use base64::Engine;
use base64::prelude::BASE64_STANDARD; use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use chrono::Utc; use chrono::Utc;
use fern::colors::ColoredLevelConfig; use fern::colors::ColoredLevelConfig;
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
@@ -59,9 +59,9 @@ use yaak_models::queries::{
}; };
use yaak_plugin_runtime::events::{ use yaak_plugin_runtime::events::{
BootResponse, CallHttpRequestActionRequest, FilterResponse, FindHttpResponsesResponse, BootResponse, CallHttpRequestActionRequest, FilterResponse, FindHttpResponsesResponse,
GetHttpRequestActionsResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, GetHttpRequestActionsResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, Icon,
InternalEvent, InternalEventPayload, RenderHttpRequestResponse, SendHttpRequestResponse, InternalEvent, InternalEventPayload, RenderHttpRequestResponse, SendHttpRequestResponse,
ShowToastRequest, ToastVariant, ShowToastRequest,
}; };
use yaak_plugin_runtime::plugin_handle::PluginHandle; use yaak_plugin_runtime::plugin_handle::PluginHandle;
use yaak_templates::{Parser, Tokens}; use yaak_templates::{Parser, Tokens};
@@ -2146,7 +2146,8 @@ async fn handle_plugin_event<R: Runtime>(
let toast_event = plugin_handle.build_event_to_send( let toast_event = plugin_handle.build_event_to_send(
&InternalEventPayload::ShowToastRequest(ShowToastRequest { &InternalEventPayload::ShowToastRequest(ShowToastRequest {
message: format!("Reloaded plugin {}", plugin_handle.dir), message: format!("Reloaded plugin {}", plugin_handle.dir),
variant: ToastVariant::Info, icon: Some(Icon::Info),
..Default::default()
}), }),
None, None,
); );
@@ -2223,9 +2224,9 @@ fn get_focused_window_no_lock<R: Runtime>(app_handle: &AppHandle<R>) -> Option<W
} }
}) })
.collect::<Vec<WebviewWindow<R>>>(); .collect::<Vec<WebviewWindow<R>>>();
if main_windows.len() == 1 { if main_windows.len() == 1 {
return main_windows.iter().next().map(|w| w.clone()) return main_windows.iter().next().map(|w| w.clone());
} }
main_windows main_windows

View File

@@ -169,27 +169,43 @@ pub struct RenderHttpRequestResponse {
#[ts(export, export_to="events.ts")] #[ts(export, export_to="events.ts")]
pub struct ShowToastRequest { pub struct ShowToastRequest {
pub message: String, pub message: String,
pub variant: ToastVariant, #[ts(optional = nullable)]
pub color: Option<Color>,
#[ts(optional = nullable)]
pub icon: Option<Icon>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[ts(export, export_to="events.ts")] #[ts(export, export_to="events.ts")]
pub enum ToastVariant { pub enum Color {
Custom, Custom,
Copied, Default,
Success, Primary,
Secondary,
Info, Info,
Success,
Notice,
Warning, Warning,
Error, Danger,
} }
impl Default for ToastVariant { impl Default for Color {
fn default() -> Self { fn default() -> Self {
ToastVariant::Info Color::Default
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export, export_to="events.ts")]
pub enum Icon {
Copy,
Info,
CheckCircle,
AlertTriangle,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to="events.ts")] #[ts(export, export_to="events.ts")]

View File

@@ -1,7 +1,7 @@
import type { Cookie } from '@yaakapp/api'; import type { Cookie } from '@yaakapp/api';
import { useCookieJars } from '../hooks/useCookieJars'; import { useCookieJars } from '../hooks/useCookieJars';
import { useUpdateCookieJar } from '../hooks/useUpdateCookieJar'; import { useUpdateCookieJar } from '../hooks/useUpdateCookieJar';
import { cookieDomain } from '../lib/models'; import { cookieDomain } from '../lib/model_util';
import { Banner } from './core/Banner'; import { Banner } from './core/Banner';
import { IconButton } from './core/IconButton'; import { IconButton } from './core/IconButton';
import { InlineCode } from './core/InlineCode'; import { InlineCode } from './core/InlineCode';

View File

@@ -67,7 +67,7 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {
iconSize="md" iconSize="md"
color="custom" color="custom"
title="Add sub environment" title="Add sub environment"
icon="plusCircle" icon="plus_circle"
iconClassName="text-text-subtlest group-hover:text-text-subtle" iconClassName="text-text-subtlest group-hover:text-text-subtle"
className="group" className="group"
onClick={handleCreateEnvironment} onClick={handleCreateEnvironment}
@@ -177,7 +177,7 @@ const EnvironmentEditor = function ({
<IconButton <IconButton
iconClassName="text-text-subtlest" iconClassName="text-text-subtlest"
size="sm" size="sm"
icon={valueVisibility.value ? 'eye' : 'eyeClosed'} icon={valueVisibility.value ? 'eye' : 'eye_closed'}
title={valueVisibility.value ? 'Hide Values' : 'Reveal Values'} title={valueVisibility.value ? 'Hide Values' : 'Reveal Values'}
onClick={() => { onClick={() => {
return valueVisibility.set((v) => !v); return valueVisibility.set((v) => !v);

View File

@@ -30,7 +30,7 @@ import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette';
import { workspacesAtom } from '../hooks/useWorkspaces'; import { workspacesAtom } from '../hooks/useWorkspaces';
import { useZoom } from '../hooks/useZoom'; import { useZoom } from '../hooks/useZoom';
import { extractKeyValue } from '../lib/keyValueStore'; import { extractKeyValue } from '../lib/keyValueStore';
import { modelsEq } from '../lib/models'; import { modelsEq } from '../lib/model_util';
import { catppuccinMacchiato } from '../lib/theme/themes/catppuccin'; import { catppuccinMacchiato } from '../lib/theme/themes/catppuccin';
import { githubLight } from '../lib/theme/themes/github'; import { githubLight } from '../lib/theme/themes/github';
import { hotdogStandDefault } from '../lib/theme/themes/hotdog-stand'; import { hotdogStandDefault } from '../lib/theme/themes/hotdog-stand';
@@ -79,16 +79,16 @@ export function GlobalHooks() {
model.model === 'http_response' model.model === 'http_response'
? httpResponsesQueryKey(model) ? httpResponsesQueryKey(model)
: model.model === 'folder' : model.model === 'folder'
? foldersQueryKey(model) ? foldersQueryKey(model)
: model.model === 'grpc_connection' : model.model === 'grpc_connection'
? grpcConnectionsQueryKey(model) ? grpcConnectionsQueryKey(model)
: model.model === 'grpc_event' : model.model === 'grpc_event'
? grpcEventsQueryKey(model) ? grpcEventsQueryKey(model)
: model.model === 'key_value' : model.model === 'key_value'
? keyValueQueryKey(model) ? keyValueQueryKey(model)
: model.model === 'cookie_jar' : model.model === 'cookie_jar'
? cookieJarsQueryKey(model) ? cookieJarsQueryKey(model)
: null; : null;
if (model.model === 'http_request' && windowLabel !== getCurrentWebviewWindow().label) { if (model.model === 'http_request' && windowLabel !== getCurrentWebviewWindow().label) {
wasUpdatedExternally(model.id); wasUpdatedExternally(model.id);

View File

@@ -6,7 +6,7 @@ import { useGrpcEvents } from '../hooks/useGrpcEvents';
import { usePinnedGrpcConnection } from '../hooks/usePinnedGrpcConnection'; import { usePinnedGrpcConnection } from '../hooks/usePinnedGrpcConnection';
import { useStateWithDeps } from '../hooks/useStateWithDeps'; import { useStateWithDeps } from '../hooks/useStateWithDeps';
import type { GrpcEvent, GrpcRequest } from '@yaakapp/api'; import type { GrpcEvent, GrpcRequest } from '@yaakapp/api';
import { isResponseLoading } from '../lib/models'; import { isResponseLoading } from '../lib/model_util';
import { Banner } from './core/Banner'; import { Banner } from './core/Banner';
import { Button } from './core/Button'; import { Button } from './core/Button';
import { Icon } from './core/Icon'; import { Icon } from './core/Icon';
@@ -197,34 +197,34 @@ function EventRow({
eventType === 'server_message' eventType === 'server_message'
? 'text-info' ? 'text-info'
: eventType === 'client_message' : eventType === 'client_message'
? 'text-primary' ? 'text-primary'
: eventType === 'error' || (status != null && status > 0) : eventType === 'error' || (status != null && status > 0)
? 'text-danger' ? 'text-danger'
: eventType === 'connection_end' : eventType === 'connection_end'
? 'text-success' ? 'text-success'
: 'text-text-subtle' : 'text-text-subtle'
} }
title={ title={
eventType === 'server_message' eventType === 'server_message'
? 'Server message' ? 'Server message'
: eventType === 'client_message' : eventType === 'client_message'
? 'Client message' ? 'Client message'
: eventType === 'error' || (status != null && status > 0) : eventType === 'error' || (status != null && status > 0)
? 'Error' ? 'Error'
: eventType === 'connection_end' : eventType === 'connection_end'
? 'Connection response' ? 'Connection response'
: undefined : undefined
} }
icon={ icon={
eventType === 'server_message' eventType === 'server_message'
? 'arrowBigDownDash' ? 'arrow_big_down_dash'
: eventType === 'client_message' : eventType === 'client_message'
? 'arrowBigUpDash' ? 'arrow_big_up_dash'
: eventType === 'error' || (status != null && status > 0) : eventType === 'error' || (status != null && status > 0)
? 'alert' ? 'alert_triangle'
: eventType === 'connection_end' : eventType === 'connection_end'
? 'check' ? 'check'
: 'info' : 'info'
} }
/> />
<div className={classNames('w-full truncate text-xs')}> <div className={classNames('w-full truncate text-xs')}>

View File

@@ -7,7 +7,7 @@ import type { ReflectResponseService } from '../hooks/useGrpc';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey'; import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest'; import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
import type { GrpcMetadataEntry, GrpcRequest } from '@yaakapp/api'; import type { GrpcMetadataEntry, GrpcRequest } from '@yaakapp/api';
import { AUTH_TYPE_BASIC, AUTH_TYPE_BEARER, AUTH_TYPE_NONE } from '../lib/models'; import { AUTH_TYPE_BASIC, AUTH_TYPE_BEARER, AUTH_TYPE_NONE } from '../lib/model_util';
import { BasicAuth } from './BasicAuth'; import { BasicAuth } from './BasicAuth';
import { BearerAuth } from './BearerAuth'; import { BearerAuth } from './BearerAuth';
import { Button } from './core/Button'; import { Button } from './core/Button';
@@ -218,7 +218,7 @@ export function GrpcConnectionSetupPane({
<Button <Button
size="sm" size="sm"
variant="border" variant="border"
rightSlot={<Icon className="text-text-subtlest" size="sm" icon="chevronDown" />} rightSlot={<Icon className="text-text-subtlest" size="sm" icon="chevron_down" />}
disabled={isStreaming || services == null} disabled={isStreaming || services == null}
className={classNames( className={classNames(
'font-mono text-editor min-w-[5rem] !ring-0', 'font-mono text-editor min-w-[5rem] !ring-0',
@@ -254,7 +254,7 @@ export function GrpcConnectionSetupPane({
title={isStreaming ? 'Connect' : 'Send'} title={isStreaming ? 'Connect' : 'Send'}
hotkeyAction="grpc_request.send" hotkeyAction="grpc_request.send"
onClick={isStreaming ? handleSend : handleConnect} onClick={isStreaming ? handleSend : handleConnect}
icon={isStreaming ? 'sendHorizontal' : 'arrowUpDown'} icon={isStreaming ? 'send_horizontal' : 'arrow_up_down'}
/> />
</> </>
) : ( ) : (
@@ -269,8 +269,8 @@ export function GrpcConnectionSetupPane({
isStreaming isStreaming
? 'x' ? 'x'
: methodType.includes('streaming') : methodType.includes('streaming')
? 'arrowUpDown' ? 'arrow_up_down'
: 'sendHorizontal' : 'send_horizontal'
} }
/> />
)} )}

View File

@@ -42,7 +42,7 @@ export function OpenWorkspaceDialog({ hide, workspace }: Props) {
<Button <Button
className="focus" className="focus"
color="secondary" color="secondary"
rightSlot={<Icon icon="externalLink" />} rightSlot={<Icon icon="external_link" />}
onClick={() => { onClick={() => {
hide(); hide();
openWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: true }); openWorkspace.mutate({ workspaceId: workspace.id, inNewWindow: true });

View File

@@ -55,7 +55,7 @@ export function RecentConnectionsDropdown({
> >
<IconButton <IconButton
title="Show connection history" title="Show connection history"
icon={activeConnection?.id === latestConnectionId ? 'chevronDown' : 'pin'} icon={activeConnection?.id === latestConnectionId ? 'chevron_down' : 'pin'}
className="ml-auto" className="ml-auto"
size="sm" size="sm"
iconSize="md" iconSize="md"

View File

@@ -87,7 +87,7 @@ export const RecentResponsesDropdown = function ResponsePane({
> >
<IconButton <IconButton
title="Show response history" title="Show response history"
icon={activeResponse?.id === latestResponseId ? 'chevronDown' : 'pin'} icon={activeResponse?.id === latestResponseId ? 'chevron_down' : 'pin'}
className="m-0.5" className="m-0.5"
size="sm" size="sm"
iconSize="md" iconSize="md"

View File

@@ -28,7 +28,7 @@ import {
BODY_TYPE_NONE, BODY_TYPE_NONE,
BODY_TYPE_OTHER, BODY_TYPE_OTHER,
BODY_TYPE_XML, BODY_TYPE_XML,
} from '../lib/models'; } from '../lib/model_util';
import { BasicAuth } from './BasicAuth'; import { BasicAuth } from './BasicAuth';
import { BearerAuth } from './BearerAuth'; import { BearerAuth } from './BearerAuth';
import { BinaryFileEditor } from './BinaryFileEditor'; import { BinaryFileEditor } from './BinaryFileEditor';

View File

@@ -21,7 +21,7 @@ export function ResponseInfo({ response }: Props) {
<IconButton <IconButton
iconSize="sm" iconSize="sm"
className="inline-block w-auto ml-1 !h-auto opacity-50 hover:opacity-100" className="inline-block w-auto ml-1 !h-auto opacity-50 hover:opacity-100"
icon="externalLink" icon="external_link"
onClick={() => open(response.url)} onClick={() => open(response.url)}
title="Open in browser" title="Open in browser"
/> />

View File

@@ -6,7 +6,7 @@ import { createGlobalState } from 'react-use';
import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders'; import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders';
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse'; import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
import { useResponseViewMode } from '../hooks/useResponseViewMode'; import { useResponseViewMode } from '../hooks/useResponseViewMode';
import { isResponseLoading } from '../lib/models'; import { isResponseLoading } from '../lib/model_util';
import { Banner } from './core/Banner'; import { Banner } from './core/Banner';
import { CountBadge } from './core/CountBadge'; import { CountBadge } from './core/CountBadge';
import { DurationTag } from './core/DurationTag'; import { DurationTag } from './core/DurationTag';

View File

@@ -38,18 +38,18 @@ const icons: IconProps['icon'][] = [
'info', 'info',
'box', 'box',
'update', 'update',
'alert', 'alert_triangle',
'arrowBigRightDash', 'arrow_big_right_dash',
'download', 'download',
'copy', 'copy',
'magicWand', 'magic_wand',
'settings', 'settings',
'trash', 'trash',
'sparkles', 'sparkles',
'pencil', 'pencil',
'paste', 'paste',
'search', 'search',
'sendHorizontal', 'send_horizontal',
]; ];
export function SettingsAppearance() { export function SettingsAppearance() {

View File

@@ -31,18 +31,18 @@ const icons: IconProps['icon'][] = [
'info', 'info',
'box', 'box',
'update', 'update',
'alert', 'alert_triangle',
'arrowBigRightDash', 'arrow_big_right_dash',
'download', 'download',
'copy', 'copy',
'magicWand', 'magic_wand',
'settings', 'settings',
'trash', 'trash',
'sparkles', 'sparkles',
'pencil', 'pencil',
'paste', 'paste',
'search', 'search',
'sendHorizontal', 'send_horizontal',
]; ];
export function SettingsDesign() { export function SettingsDesign() {

View File

@@ -64,13 +64,13 @@ export function SettingsDropdown() {
{ {
key: 'import-data', key: 'import-data',
label: 'Import Data', label: 'Import Data',
leftSlot: <Icon icon="folderInput" />, leftSlot: <Icon icon="folder_input" />,
onSelect: () => importData.mutate(), onSelect: () => importData.mutate(),
}, },
{ {
key: 'export-data', key: 'export-data',
label: 'Export Data', label: 'Export Data',
leftSlot: <Icon icon="folderOutput" />, leftSlot: <Icon icon="folder_output" />,
onSelect: () => exportData.mutate(), onSelect: () => exportData.mutate(),
}, },
{ type: 'separator', label: `Yaak v${appInfo.version}` }, { type: 'separator', label: `Yaak v${appInfo.version}` },
@@ -84,14 +84,14 @@ export function SettingsDropdown() {
key: 'feedback', key: 'feedback',
label: 'Feedback', label: 'Feedback',
leftSlot: <Icon icon="chat" />, leftSlot: <Icon icon="chat" />,
rightSlot: <Icon icon="externalLink" />, rightSlot: <Icon icon="external_link" />,
onSelect: () => open('https://yaak.app/roadmap'), onSelect: () => open('https://yaak.app/roadmap'),
}, },
{ {
key: 'changelog', key: 'changelog',
label: 'Changelog', label: 'Changelog',
leftSlot: <Icon icon="cake" />, leftSlot: <Icon icon="cake" />,
rightSlot: <Icon icon="externalLink" />, rightSlot: <Icon icon="external_link" />,
onSelect: () => open(`https://yaak.app/changelog/${appInfo.version}`), onSelect: () => open(`https://yaak.app/changelog/${appInfo.version}`),
}, },
]} ]}

View File

@@ -34,7 +34,7 @@ import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest'; import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
import { useWorkspaces } from '../hooks/useWorkspaces'; import { useWorkspaces } from '../hooks/useWorkspaces';
import { fallbackRequestName } from '../lib/fallbackRequestName'; import { fallbackRequestName } from '../lib/fallbackRequestName';
import { isResponseLoading } from '../lib/models'; import { isResponseLoading } from '../lib/model_util';
import { getHttpRequest } from '../lib/store'; import { getHttpRequest } from '../lib/store';
import type { DropdownItem } from './core/Dropdown'; import type { DropdownItem } from './core/Dropdown';
import { ContextMenu } from './core/Dropdown'; import { ContextMenu } from './core/Dropdown';
@@ -433,32 +433,31 @@ export function Sidebar({ className }: Props) {
onBlur={handleBlur} onBlur={handleBlur}
tabIndex={hidden ? -1 : 0} tabIndex={hidden ? -1 : 0}
onContextMenu={handleMainContextMenu} onContextMenu={handleMainContextMenu}
className={classNames( className={classNames(className, 'h-full grid grid-rows-[minmax(0,1fr)_auto]')}
className,
'h-full pb-3 overflow-y-scroll overflow-x-visible hide-scrollbars pt-2',
)}
> >
<ContextMenu <div className="pb-3 overflow-x-visible overflow-y-scroll pt-2">
triggerPosition={showMainContextMenu} <ContextMenu
items={mainContextMenuItems} triggerPosition={showMainContextMenu}
onClose={() => setShowMainContextMenu(null)} items={mainContextMenuItems}
/> onClose={() => setShowMainContextMenu(null)}
<SidebarItems />
treeParentMap={treeParentMap} <SidebarItems
activeId={activeRequest?.id ?? null} treeParentMap={treeParentMap}
selectedId={selectedId} activeId={activeRequest?.id ?? null}
selectedTree={selectedTree} selectedId={selectedId}
isCollapsed={isCollapsed} selectedTree={selectedTree}
tree={tree} isCollapsed={isCollapsed}
focused={hasFocus} tree={tree}
draggingId={draggingId} focused={hasFocus}
onSelect={handleSelect} draggingId={draggingId}
hoveredIndex={hoveredIndex} onSelect={handleSelect}
hoveredTree={hoveredTree} hoveredIndex={hoveredIndex}
handleMove={handleMove} hoveredTree={hoveredTree}
handleEnd={handleEnd} handleMove={handleMove}
handleDragStart={handleDragStart} handleEnd={handleEnd}
/> handleDragStart={handleDragStart}
/>
</div>
</aside> </aside>
); );
} }
@@ -741,7 +740,7 @@ function SidebarItem({
{ {
key: 'sendAll', key: 'sendAll',
label: 'Send All', label: 'Send All',
leftSlot: <Icon icon="sendHorizontal" />, leftSlot: <Icon icon="send_horizontal" />,
onSelect: () => sendManyRequests.mutate(child.children.map((c) => c.item.id)), onSelect: () => sendManyRequests.mutate(child.children.map((c) => c.item.id)),
}, },
{ {
@@ -784,7 +783,7 @@ function SidebarItem({
label: 'Send', label: 'Send',
hotKeyAction: 'http_request.send', hotKeyAction: 'http_request.send',
hotKeyLabelOnly: true, // Already bound in URL bar hotKeyLabelOnly: true, // Already bound in URL bar
leftSlot: <Icon icon="sendHorizontal" />, leftSlot: <Icon icon="send_horizontal" />,
onSelect: () => sendRequest.mutate(itemId), onSelect: () => sendRequest.mutate(itemId),
}, },
...httpRequestActions.map((a) => ({ ...httpRequestActions.map((a) => ({
@@ -822,7 +821,7 @@ function SidebarItem({
{ {
key: 'moveWorkspace', key: 'moveWorkspace',
label: 'Move', label: 'Move',
leftSlot: <Icon icon="arrowRightCircle" />, leftSlot: <Icon icon="arrow_right_circle" />,
hidden: workspaces.length <= 1, hidden: workspaces.length <= 1,
onSelect: moveToWorkspace.mutate, onSelect: moveToWorkspace.mutate,
}, },
@@ -882,7 +881,7 @@ function SidebarItem({
{itemModel === 'folder' && ( {itemModel === 'folder' && (
<Icon <Icon
size="sm" size="sm"
icon="chevronRight" icon="chevron_right"
className={classNames( className={classNames(
'text-text-subtlest', 'text-text-subtlest',
'transition-transform', 'transition-transform',

View File

@@ -31,10 +31,10 @@ export function SidebarActions() {
className="pointer-events-auto" className="pointer-events-auto"
size="sm" size="sm"
title="Show sidebar" title="Show sidebar"
icon={hidden ? 'leftPanelHidden' : 'leftPanelVisible'} icon={hidden ? 'left_panel_hidden' : 'left_panel_visible'}
/> />
<CreateDropdown hotKeyAction="http_request.create"> <CreateDropdown hotKeyAction="http_request.create">
<IconButton size="sm" icon="plusCircle" title="Add Resource" /> <IconButton size="sm" icon="plus_circle" title="Add Resource" />
</CreateDropdown> </CreateDropdown>
</HStack> </HStack>
); );

View File

@@ -1,12 +1,12 @@
import { AnimatePresence } from 'framer-motion';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import React, { createContext, useContext, useMemo, useRef, useState } from 'react'; import React, { createContext, useContext, useMemo, useRef, useState } from 'react';
import type { ShowToastRequest } from '../../plugin-runtime-types/src'; import type { ShowToastRequest } from '@yaakapp/api';
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent'; import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
import { generateId } from '../lib/generateId';
import type { ToastProps } from './core/Toast'; import type { ToastProps } from './core/Toast';
import { Toast } from './core/Toast'; import { Toast } from './core/Toast';
import { generateId } from '../lib/generateId';
import { Portal } from './Portal'; import { Portal } from './Portal';
import { AnimatePresence } from 'framer-motion';
type ToastEntry = { type ToastEntry = {
id?: string; id?: string;
@@ -93,7 +93,7 @@ export const Toasts = () => {
const { toasts } = useContext(ToastContext); const { toasts } = useContext(ToastContext);
return ( return (
<Portal name="toasts"> <Portal name="toasts">
<div className="absolute right-0 bottom-0 z-10"> <div className="absolute right-0 bottom-0 z-20">
<AnimatePresence> <AnimatePresence>
{toasts.map((props: PrivateToastEntry) => ( {toasts.map((props: PrivateToastEntry) => (
<ToastInstance key={props.id} {...props} /> <ToastInstance key={props.id} {...props} />

View File

@@ -37,7 +37,7 @@ export const UrlBar = memo(function UrlBar({
onCancel, onCancel,
onMethodChange, onMethodChange,
onPaste, onPaste,
submitIcon = 'sendHorizontal', submitIcon = 'send_horizontal',
autocomplete, autocomplete,
rightSlot, rightSlot,
isLoading, isLoading,

View File

@@ -26,7 +26,7 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
<CookieDropdown /> <CookieDropdown />
<HStack> <HStack>
<WorkspaceActionsDropdown /> <WorkspaceActionsDropdown />
<Icon icon="chevronRight" className="text-text-subtle" /> <Icon icon="chevron_right" className="text-text-subtle" />
<EnvironmentActionsDropdown className="w-auto pointer-events-auto" /> <EnvironmentActionsDropdown className="w-auto pointer-events-auto" />
</HStack> </HStack>
</HStack> </HStack>

View File

@@ -14,7 +14,7 @@ export function Banner({ children, className, color = 'secondary' }: Props) {
className={classNames( className={classNames(
className, className,
`x-theme-banner--${color}`, `x-theme-banner--${color}`,
'border border-dashed border-border-subtle bg-surface-highlight', 'border border-dashed border-border-subtle bg-surface',
'italic px-3 py-2 rounded select-auto cursor-text', 'italic px-3 py-2 rounded select-auto cursor-text',
'overflow-x-auto text-text', 'overflow-x-auto text-text',
)} )}

View File

@@ -108,6 +108,11 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
className={classes} className={classes}
disabled={disabled || isLoading} disabled={disabled || isLoading}
onClick={onClick} onClick={onClick}
onDoubleClick={(e) => {
// Kind of a hack? This prevents double-clicks from going through buttons. For example, when
// double-clicking the workspace header to toggle window maximization
e.stopPropagation();
}}
title={fullTitle} title={fullTitle}
{...props} {...props}
> >
@@ -126,7 +131,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
{children} {children}
</div> </div>
{rightSlot && <div className="ml-1">{rightSlot}</div>} {rightSlot && <div className="ml-1">{rightSlot}</div>}
{forDropdown && <Icon icon="chevronDown" size={size} className="ml-1 -mr-1" />} {forDropdown && <Icon icon="chevron_down" size={size} className="ml-1 -mr-1" />}
</button> </button>
); );
}); });

View File

@@ -191,7 +191,7 @@ export const ContextMenu = forwardRef<DropdownRef, ContextMenuProps>(function Co
return ( return (
<Menu <Menu
isOpen // Always open because we return null if not isOpen={true} // Always open because we return null if not
className={className} className={className}
ref={ref} ref={ref}
items={items} items={items}
@@ -361,29 +361,38 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
}>(() => { }>(() => {
if (triggerShape == null) return { containerStyles: {}, triangleStyles: null }; if (triggerShape == null) return { containerStyles: {}, triangleStyles: null };
const menuMarginY = 5;
const docRect = document.documentElement.getBoundingClientRect(); const docRect = document.documentElement.getBoundingClientRect();
const width = triggerShape.right - triggerShape.left; const width = triggerShape.right - triggerShape.left;
const heightAbove = triggerShape.top; const heightAbove = triggerShape.top;
const heightBelow = docRect.height - triggerShape.bottom; const heightBelow = docRect.height - triggerShape.bottom;
const hSpaceRemaining = docRect.width - triggerShape.left; const horizontalSpaceRemaining = docRect.width - triggerShape.left;
const top = triggerShape.bottom + 5; const top = triggerShape.bottom;
const onRight = hSpaceRemaining < 200; const onRight = horizontalSpaceRemaining < 200;
const upsideDown = heightAbove > heightBelow && heightBelow < 200; const upsideDown = heightBelow < heightAbove && heightBelow < items.length * 25 + 20 + 200;
const triggerWidth = triggerShape.right - triggerShape.left; const triggerWidth = triggerShape.right - triggerShape.left;
const containerStyles = { const containerStyles = {
top: !upsideDown ? top : undefined, top: !upsideDown ? top + menuMarginY : undefined,
bottom: upsideDown ? docRect.height - top : undefined, bottom: upsideDown
? docRect.height - top - (triggerShape.top - triggerShape.bottom) + menuMarginY
: undefined,
right: onRight ? docRect.width - triggerShape.right : undefined, right: onRight ? docRect.width - triggerShape.right : undefined,
left: !onRight ? triggerShape.left : undefined, left: !onRight ? triggerShape.left : undefined,
minWidth: fullWidth ? triggerWidth : undefined, minWidth: fullWidth ? triggerWidth : undefined,
maxWidth: '40rem', maxWidth: '40rem',
}; };
const size = { top: '-0.2rem', width: '0.4rem', height: '0.4rem' }; const triangleStyles: CSSProperties = {
const triangleStyles = onRight width: '0.4rem',
? { right: width / 2, marginRight: '-0.2rem', ...size } height: '0.4rem',
: { left: width / 2, marginLeft: '-0.2rem', ...size }; ...(onRight
? { right: width / 2, marginRight: '-0.2rem' }
: { left: width / 2, marginLeft: '-0.2rem' }),
...(upsideDown
? { bottom: '-0.2rem', rotate: '225deg' }
: { top: '-0.2rem', rotate: '45deg' }),
};
return { containerStyles, triangleStyles }; return { containerStyles, triangleStyles };
}, [fullWidth, triggerShape]); }, [fullWidth, items.length, triggerShape]);
const filteredItems = useMemo( const filteredItems = useMemo(
() => items.filter((i) => getNodeText(i.label).toLowerCase().includes(filter.toLowerCase())), () => items.filter((i) => getNodeText(i.label).toLowerCase().includes(filter.toLowerCase())),
@@ -415,7 +424,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
), ),
)} )}
{isOpen && ( {isOpen && (
<Overlay open variant="transparent" portalName="dropdown" zIndex={50}> <Overlay open={true} variant="transparent" portalName="dropdown" zIndex={50}>
<div className="x-theme-menu"> <div className="x-theme-menu">
<div tabIndex={-1} aria-hidden className="fixed inset-0 z-30" onClick={handleClose} /> <div tabIndex={-1} aria-hidden className="fixed inset-0 z-30" onClick={handleClose} />
<motion.div <motion.div
@@ -433,7 +442,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
<span <span
aria-hidden aria-hidden
style={triangleStyles} style={triangleStyles}
className="bg-surface absolute rotate-45 border-border-subtle border-t border-l" className="bg-surface absolute border-border-subtle border-t border-l"
/> />
)} )}
{containerStyles && ( {containerStyles && (
@@ -443,7 +452,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
className={classNames( className={classNames(
className, className,
'h-auto bg-surface rounded-md shadow-lg py-1.5 border', 'h-auto bg-surface rounded-md shadow-lg py-1.5 border',
'border-border-subtle overflow-auto mb-1 mx-0.5', 'border-border-subtle overflow-auto mx-0.5',
)} )}
> >
{filter && ( {filter && (

View File

@@ -346,7 +346,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
key="format" key="format"
size="sm" size="sm"
title="Reformat contents" title="Reformat contents"
icon="magicWand" icon="magic_wand"
variant="border" variant="border"
className={classNames(actionClassName)} className={classNames(actionClassName)}
onClick={() => { onClick={() => {

View File

@@ -4,62 +4,69 @@ import type { HTMLAttributes } from 'react';
import { memo } from 'react'; import { memo } from 'react';
const icons = { const icons = {
alert: lucide.AlertTriangleIcon, alert_triangle: lucide.AlertTriangleIcon,
archive: lucide.ArchiveIcon, archive: lucide.ArchiveIcon,
arrowBigDownDash: lucide.ArrowBigDownDashIcon, arrow_big_down_dash: lucide.ArrowBigDownDashIcon,
arrowRightCircle: lucide.ArrowRightCircleIcon, arrow_right_circle: lucide.ArrowRightCircleIcon,
arrowBigLeftDash: lucide.ArrowBigLeftDashIcon, arrow_big_left_dash: lucide.ArrowBigLeftDashIcon,
arrowBigRight: lucide.ArrowBigRightIcon, arrow_big_right: lucide.ArrowBigRightIcon,
arrowBigRightDash: lucide.ArrowBigRightDashIcon, arrow_big_right_dash: lucide.ArrowBigRightDashIcon,
arrowBigUpDash: lucide.ArrowBigUpDashIcon, arrow_big_up_dash: lucide.ArrowBigUpDashIcon,
arrowDown: lucide.ArrowDownIcon, arrow_down: lucide.ArrowDownIcon,
arrowDownToDot: lucide.ArrowDownToDotIcon, arrow_down_to_dot: lucide.ArrowDownToDotIcon,
arrowUp: lucide.ArrowUpIcon, arrow_up: lucide.ArrowUpIcon,
arrowUpDown: lucide.ArrowUpDownIcon, arrow_up_down: lucide.ArrowUpDownIcon,
arrowUpFromDot: lucide.ArrowUpFromDotIcon, arrow_up_from_dot: lucide.ArrowUpFromDotIcon,
badge_check: lucide.BadgeCheckIcon,
box: lucide.BoxIcon, box: lucide.BoxIcon,
cake: lucide.CakeIcon, cake: lucide.CakeIcon,
chat: lucide.MessageSquare, chat: lucide.MessageSquare,
check: lucide.CheckIcon, check: lucide.CheckIcon,
checkCircle: lucide.CheckCircleIcon, check_circle: lucide.CheckCircleIcon,
chevronDown: lucide.ChevronDownIcon, chevron_down: lucide.ChevronDownIcon,
chevronRight: lucide.ChevronRightIcon, chevron_right: lucide.ChevronRightIcon,
circle_alert: lucide.CircleAlertIcon,
cloud: lucide.CloudIcon,
code: lucide.CodeIcon, code: lucide.CodeIcon,
cookie: lucide.CookieIcon, cookie: lucide.CookieIcon,
copy: lucide.CopyIcon, copy: lucide.CopyIcon,
copyCheck: lucide.CopyCheck, copy_check: lucide.CopyCheck,
download: lucide.DownloadIcon, download: lucide.DownloadIcon,
externalLink: lucide.ExternalLinkIcon, external_link: lucide.ExternalLinkIcon,
eye: lucide.EyeIcon, eye: lucide.EyeIcon,
eyeClosed: lucide.EyeOffIcon, eye_closed: lucide.EyeOffIcon,
fileCode: lucide.FileCodeIcon, file_code: lucide.FileCodeIcon,
filter: lucide.FilterIcon, filter: lucide.FilterIcon,
flask: lucide.FlaskConicalIcon, flask: lucide.FlaskConicalIcon,
folderInput: lucide.FolderInputIcon, folder_input: lucide.FolderInputIcon,
folderOutput: lucide.FolderOutputIcon, folder_output: lucide.FolderOutputIcon,
gripVertical: lucide.GripVerticalIcon, git_branch: lucide.GitBranchIcon,
git_commit: lucide.GitCommitIcon,
git_commit_vertical: lucide.GitCommitVerticalIcon,
git_pull_request: lucide.GitPullRequestIcon,
git_fork: lucide.GitForkIcon,
grip_vertical: lucide.GripVerticalIcon,
hand: lucide.HandIcon, hand: lucide.HandIcon,
help: lucide.CircleHelpIcon, help: lucide.CircleHelpIcon,
house: lucide.HomeIcon, house: lucide.HomeIcon,
info: lucide.InfoIcon, info: lucide.InfoIcon,
keyboard: lucide.KeyboardIcon, keyboard: lucide.KeyboardIcon,
leftPanelHidden: lucide.PanelLeftOpenIcon, left_panel_hidden: lucide.PanelLeftOpenIcon,
leftPanelVisible: lucide.PanelLeftCloseIcon, left_panel_visible: lucide.PanelLeftCloseIcon,
magicWand: lucide.Wand2Icon, magic_wand: lucide.Wand2Icon,
minus: lucide.MinusIcon, minus: lucide.MinusIcon,
moon: lucide.MoonIcon, moon: lucide.MoonIcon,
moreVertical: lucide.MoreVerticalIcon, more_vertical: lucide.MoreVerticalIcon,
paste: lucide.ClipboardPasteIcon, paste: lucide.ClipboardPasteIcon,
pencil: lucide.PencilIcon, pencil: lucide.PencilIcon,
pin: lucide.PinIcon, pin: lucide.PinIcon,
plug: lucide.Plug, plug: lucide.Plug,
plus: lucide.PlusIcon, plus: lucide.PlusIcon,
plusCircle: lucide.PlusCircleIcon, plus_circle: lucide.PlusCircleIcon,
refresh: lucide.RefreshCwIcon, refresh: lucide.RefreshCwIcon,
save: lucide.SaveIcon, save: lucide.SaveIcon,
search: lucide.SearchIcon, search: lucide.SearchIcon,
sendHorizontal: lucide.SendHorizonalIcon, send_horizontal: lucide.SendHorizonalIcon,
settings2: lucide.Settings2Icon,
settings: lucide.SettingsIcon, settings: lucide.SettingsIcon,
sparkles: lucide.SparklesIcon, sparkles: lucide.SparklesIcon,
sun: lucide.SunIcon, sun: lucide.SunIcon,

View File

@@ -193,7 +193,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
className="mr-0.5 group/obscure !h-auto my-0.5" className="mr-0.5 group/obscure !h-auto my-0.5"
iconClassName="text-text-subtle group-hover/obscure:text" iconClassName="text-text-subtle group-hover/obscure:text"
iconSize="sm" iconSize="sm"
icon={obscured ? 'eye' : 'eyeClosed'} icon={obscured ? 'eye' : 'eye_closed'}
onClick={() => setObscured((o) => !o)} onClick={() => setObscured((o) => !o)}
/> />
)} )}

View File

@@ -101,7 +101,7 @@ export const JsonAttributeTree = ({
<button className="group relative flex items-center pl-4 w-full" onClick={toggleExpanded}> <button className="group relative flex items-center pl-4 w-full" onClick={toggleExpanded}>
<Icon <Icon
size="xs" size="xs"
icon="chevronRight" icon="chevron_right"
className={classNames( className={classNames(
'left-0 absolute transition-transform flex items-center', 'left-0 absolute transition-transform flex items-center',
'text-text-subtlest group-hover:text-text-subtle', 'text-text-subtlest group-hover:text-text-subtle',

View File

@@ -22,7 +22,7 @@ export function Link({ href, children, className, ...other }: Props) {
{...other} {...other}
> >
<span className="underline">{children}</span> <span className="underline">{children}</span>
<Icon className="inline absolute right-0.5 top-0.5" size="xs" icon="externalLink" /> <Icon className="inline absolute right-0.5 top-0.5" size="xs" icon="external_link" />
</a> </a>
); );
} }

View File

@@ -397,7 +397,7 @@ function PairEditorRow({
'justify-center opacity-0 group-hover:opacity-70', 'justify-center opacity-0 group-hover:opacity-70',
)} )}
> >
<Icon size="sm" icon="gripVertical" className="pointer-events-none" /> <Icon size="sm" icon="grip_vertical" className="pointer-events-none" />
</div> </div>
) : ( ) : (
<span className="w-3" /> <span className="w-3" />
@@ -545,7 +545,7 @@ function PairEditorRow({
<IconButton <IconButton
iconSize="sm" iconSize="sm"
size="xs" size="xs"
icon={isLast ? 'empty' : 'chevronDown'} icon={isLast ? 'empty' : 'chevron_down'}
title="Select form data type" title="Select form data type"
/> />
</RadioDropdown> </RadioDropdown>
@@ -563,7 +563,7 @@ function PairEditorRow({
<IconButton <IconButton
iconSize="sm" iconSize="sm"
size="xs" size="xs"
icon={isLast ? 'empty' : 'chevronDown'} icon={isLast ? 'empty' : 'chevron_down'}
title="Select form data type" title="Select form data type"
/> />
</Dropdown> </Dropdown>

View File

@@ -33,7 +33,7 @@ export const PairOrBulkEditor = forwardRef<PairEditorRef, Props>(function PairOr
'bg-surface text-text-subtle hover:text group-hover/wrapper:opacity-100', 'bg-surface text-text-subtle hover:text group-hover/wrapper:opacity-100',
)} )}
onClick={() => setUseBulk((b) => !b)} onClick={() => setUseBulk((b) => !b)}
icon={useBulk ? 'table' : 'fileCode'} icon={useBulk ? 'table' : 'file_code'}
/> />
</div> </div>
</div> </div>

View File

@@ -143,7 +143,7 @@ export const PlainInput = forwardRef<HTMLInputElement, PlainInputProps>(function
className="mr-0.5 group/obscure !h-auto my-0.5" className="mr-0.5 group/obscure !h-auto my-0.5"
iconClassName="text-text-subtle group-hover/obscure:text" iconClassName="text-text-subtle group-hover/obscure:text"
iconSize="sm" iconSize="sm"
icon={obscured ? 'eye' : 'eyeClosed'} icon={obscured ? 'eye' : 'eye_closed'}
onClick={() => setObscured((o) => !o)} onClick={() => setObscured((o) => !o)}
/> />
)} )}

View File

@@ -100,10 +100,10 @@ export function Tabs({
> >
{option && 'shortLabel' in option {option && 'shortLabel' in option
? option.shortLabel ? option.shortLabel
: option?.label ?? 'Unknown'} : (option?.label ?? 'Unknown')}
<Icon <Icon
size="sm" size="sm"
icon="chevronDown" icon="chevron_down"
className={classNames('ml-1', isActive ? 'text-text-subtle' : 'opacity-50')} className={classNames('ml-1', isActive ? 'text-text-subtle' : 'opacity-50')}
/> />
</button> </button>

View File

@@ -1,3 +1,4 @@
import type { ShowToastRequest } from '@yaakapp/api';
import classNames from 'classnames'; import classNames from 'classnames';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
@@ -15,27 +16,23 @@ export interface ToastProps {
className?: string; className?: string;
timeout: number | null; timeout: number | null;
action?: ReactNode; action?: ReactNode;
variant?: 'custom' | 'copied' | 'success' | 'info' | 'warning' | 'error'; icon?: ShowToastRequest['icon'];
color?: ShowToastRequest['color'];
} }
const ICONS: Record<NonNullable<ToastProps['variant']>, IconProps['icon'] | null> = { const ICONS: Record<NonNullable<ToastProps['color']>, IconProps['icon'] | null> = {
custom: null, custom: null,
copied: 'copyCheck', default: 'info',
warning: 'alert', danger: 'alert_triangle',
error: 'alert',
info: 'info', info: 'info',
success: 'checkCircle', notice: 'alert_triangle',
primary: 'info',
secondary: 'info',
success: 'check_circle',
warning: 'alert_triangle',
}; };
export function Toast({ export function Toast({ children, open, onClose, timeout, action, icon, color }: ToastProps) {
children,
className,
open,
onClose,
timeout,
action,
variant = 'info',
}: ToastProps) {
useKey( useKey(
'Escape', 'Escape',
() => { () => {
@@ -45,8 +42,9 @@ export function Toast({
{}, {},
[open], [open],
); );
color = color ?? 'default';
const icon = variant in ICONS && ICONS[variant]; const toastIcon = icon ?? (color in ICONS && ICONS[color]);
return ( return (
<motion.div <motion.div
@@ -54,55 +52,46 @@ export function Toast({
animate={{ opacity: 100, right: 0 }} animate={{ opacity: 100, right: 0 }}
exit={{ opacity: 0, right: '-100%' }} exit={{ opacity: 0, right: '-100%' }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className={classNames( className={classNames('bg-surface m-2 rounded-lg')}
className,
'x-theme-toast',
'pointer-events-auto',
'relative bg-surface pointer-events-auto',
'rounded-lg',
'border border-border-subtle shadow-lg',
'max-w-[calc(100vw-5rem)] max-h-[calc(100vh-6rem)]',
'w-[22rem] max-h-[80vh]',
'm-2 grid grid-cols-[1fr_auto]',
'text',
)}
> >
<div className="px-3 py-3 flex items-center gap-2"> <div
{icon && ( className={classNames(
<Icon `x-theme-toast x-theme-toast--${color}`,
icon={icon} 'pointer-events-auto overflow-hidden',
className={classNames( 'relative pointer-events-auto bg-surface text-text rounded-lg',
variant === 'success' && 'text-success', 'border border-border shadow-lg',
variant === 'warning' && 'text-warning', 'grid grid-cols-[1fr_auto]',
variant === 'error' && 'text-danger', 'text',
variant === 'copied' && 'text-primary',
)}
/>
)} )}
<VStack space={2}> >
<div>{children}</div> <div className="px-3 py-3 flex items-center gap-2">
{action} {toastIcon && <Icon icon={toastIcon} className="text-text-subtle" />}
</VStack> <VStack space={2}>
</div> <div>{children}</div>
{action}
<IconButton </VStack>
color="custom"
className="opacity-60"
title="Dismiss"
icon="x"
onClick={onClose}
/>
{timeout != null && (
<div className="w-full absolute bottom-0 left-0 right-0">
<motion.div
className="bg-surface-highlight h-0.5"
initial={{ width: '100%' }}
animate={{ width: '0%', opacity: 0.2 }}
transition={{ duration: timeout / 1000, ease: 'linear' }}
/>
</div> </div>
)}
<IconButton
color={color}
variant="border"
className="opacity-60 border-0"
title="Dismiss"
icon="x"
onClick={onClose}
/>
{timeout != null && (
<div className="w-full absolute bottom-0 left-0 right-0">
<motion.div
className="bg-surface-highlight h-[3px]"
initial={{ width: '100%' }}
animate={{ width: '0%', opacity: 0.2 }}
transition={{ duration: timeout / 1000, ease: 'linear' }}
/>
</div>
)}
</div>
</motion.div> </motion.div>
); );
} }

View File

@@ -1,6 +1,6 @@
import { useSaveResponse } from '../../hooks/useSaveResponse'; import { useSaveResponse } from '../../hooks/useSaveResponse';
import type { HttpResponse } from '@yaakapp/api'; import type { HttpResponse } from '@yaakapp/api';
import { getContentTypeHeader } from '../../lib/models'; import { getContentTypeHeader } from '../../lib/model_util';
import { Banner } from '../core/Banner'; import { Banner } from '../core/Banner';
import { Button } from '../core/Button'; import { Button } from '../core/Button';
import { InlineCode } from '../core/InlineCode'; import { InlineCode } from '../core/InlineCode';

View File

@@ -15,7 +15,8 @@ export function useCopy({ disableToast }: { disableToast?: boolean } = {}) {
if (text != '' && !disableToast) { if (text != '' && !disableToast) {
toast.show({ toast.show({
id: 'copied', id: 'copied',
variant: 'copied', color: 'secondary',
icon: 'copy',
message: 'Copied to clipboard', message: 'Copied to clipboard',
}); });
} }

View File

@@ -1,7 +1,7 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import type { DropdownItem } from '../components/core/Dropdown'; import type { DropdownItem } from '../components/core/Dropdown';
import { Icon } from '../components/core/Icon'; import { Icon } from '../components/core/Icon';
import { BODY_TYPE_GRAPHQL } from '../lib/models'; import { BODY_TYPE_GRAPHQL } from '../lib/model_util';
import { useCreateFolder } from './useCreateFolder'; import { useCreateFolder } from './useCreateFolder';
import { useCreateGrpcRequest } from './useCreateGrpcRequest'; import { useCreateGrpcRequest } from './useCreateGrpcRequest';
import { useCreateHttpRequest } from './useCreateHttpRequest'; import { useCreateHttpRequest } from './useCreateHttpRequest';

View File

@@ -33,7 +33,7 @@ export function useExportData() {
activeWorkspace={activeWorkspace} activeWorkspace={activeWorkspace}
onSuccess={() => { onSuccess={() => {
toast.show({ toast.show({
variant: 'success', color: 'success',
message: 'Data export successful', message: 'Data export successful',
}); });
}} }}

View File

@@ -39,7 +39,7 @@ export function useImportCurl() {
} }
toast.show({ toast.show({
variant: 'success', color: 'success',
message: `${verb} request from Curl`, message: `${verb} request from Curl`,
}); });
}, },

View File

@@ -51,7 +51,7 @@ export function useImportQuerystring(requestId: string) {
if (additionalUrlParameters.length > 0) { if (additionalUrlParameters.length > 0) {
toast.show({ toast.show({
id: 'querystring-imported', id: 'querystring-imported',
variant: 'info', color: 'info',
message: `Imported ${additionalUrlParameters.length} ${pluralize('param', additionalUrlParameters.length)} from URL`, message: `Imported ${additionalUrlParameters.length} ${pluralize('param', additionalUrlParameters.length)} from URL`,
}); });
} }

View File

@@ -1,4 +1,4 @@
import { isResponseLoading } from '../lib/models'; import { isResponseLoading } from '../lib/model_util';
import { useLatestHttpResponse } from './useLatestHttpResponse'; import { useLatestHttpResponse } from './useLatestHttpResponse';
export function useIsResponseLoading(requestId: string | null): boolean { export function useIsResponseLoading(requestId: string | null): boolean {

View File

@@ -27,7 +27,7 @@ export function useNotificationToast() {
id: payload.id, id: payload.id,
timeout: null, timeout: null,
message: payload.message, message: payload.message,
variant: 'custom', color: 'custom',
onClose: () => markRead(payload.id), onClose: () => markRead(payload.id),
action: action:
actionLabel && actionUrl ? ( actionLabel && actionUrl ? (

View File

@@ -5,7 +5,7 @@ import slugify from 'slugify';
import { InlineCode } from '../components/core/InlineCode'; import { InlineCode } from '../components/core/InlineCode';
import { useToast } from '../components/ToastContext'; import { useToast } from '../components/ToastContext';
import type { HttpResponse } from '@yaakapp/api'; import type { HttpResponse } from '@yaakapp/api';
import { getContentTypeHeader } from '../lib/models'; import { getContentTypeHeader } from '../lib/model_util';
import { getHttpRequest } from '../lib/store'; import { getHttpRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';

View File

@@ -1,6 +1,6 @@
import { readFile } from '@tauri-apps/plugin-fs'; import { readFile } from '@tauri-apps/plugin-fs';
import type { HttpResponse } from '@yaakapp/api'; import type { HttpResponse } from '@yaakapp/api';
import { getCharsetFromContentType } from './models'; import { getCharsetFromContentType } from './model_util';
export async function getResponseBodyText(response: HttpResponse): Promise<string | null> { export async function getResponseBodyText(response: HttpResponse): Promise<string | null> {
if (response.bodyPath) { if (response.bodyPath) {

View File

@@ -98,14 +98,23 @@ function templateTagColorVariables(color: YaakColor): Partial<CSSVariables> {
}; };
} }
function toastColorVariables(color: YaakColor): Partial<CSSVariables> {
return {
text: color.lift(0.8),
textSubtle: color,
surface: color.translucify(0.9),
surfaceHighlight: color.translucify(0.8),
border: color.lift(0.3).translucify(0.6),
};
}
function bannerColorVariables(color: YaakColor): Partial<CSSVariables> { function bannerColorVariables(color: YaakColor): Partial<CSSVariables> {
return { return {
text: color.lift(0.8), text: color.lift(0.8),
textSubtle: color.translucify(0.3), textSubtle: color.translucify(0.3),
textSubtlest: color, textSubtlest: color,
surface: color, surface: color.translucify(0.9),
border: color.lift(0.3).translucify(0.4), border: color.lift(0.3).translucify(0.4),
surfaceHighlight: color.translucify(0.9),
}; };
} }
@@ -201,6 +210,15 @@ function bannerCSS(color: YaakColorKey, colors?: Partial<YaakColors>): string |
); );
} }
function toastCSS(color: YaakColorKey, colors?: Partial<YaakColors>): string | null {
const yaakColor = colors?.[color];
if (yaakColor == null) {
return null;
}
return [variablesToCSS(`.x-theme-toast--${color}`, toastColorVariables(yaakColor))].join('\n\n');
}
function templateTagCSS(color: YaakColorKey, colors?: Partial<YaakColors>): string | null { function templateTagCSS(color: YaakColorKey, colors?: Partial<YaakColors>): string | null {
const yaakColor = colors?.[color]; const yaakColor = colors?.[color];
if (yaakColor == null) { if (yaakColor == null) {
@@ -253,6 +271,9 @@ export function getThemeCSS(theme: YaakTheme): string {
...Object.keys(colors ?? {}).map((key) => ...Object.keys(colors ?? {}).map((key) =>
bannerCSS(key as YaakColorKey, theme.components?.banner ?? colors), bannerCSS(key as YaakColorKey, theme.components?.banner ?? colors),
), ),
...Object.keys(colors ?? {}).map((key) =>
toastCSS(key as YaakColorKey, theme.components?.banner ?? colors),
),
...Object.keys(colors ?? {}).map((key) => ...Object.keys(colors ?? {}).map((key) =>
templateTagCSS(key as YaakColorKey, theme.components?.templateTag ?? colors), templateTagCSS(key as YaakColorKey, theme.components?.templateTag ?? colors),
), ),