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-os": "^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",
"classnames": "^2.5.1",
"cm6-graphql": "^0.0.9",

View File

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

View File

@@ -26,6 +26,8 @@ export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunc
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 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 Icon = "copy" | "info" | "check_circle" | "alert_triangle";
export type ImportRequest = { content: string, };
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 ShowToastRequest = { message: string, variant: ToastVariant, };
export type ShowToastRequest = { message: string, color?: Color | null, icon?: Icon | null, };
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 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 './themes';
export * from './gen/models';
export * from './gen/model_util';
export * from './gen/events';

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

@@ -10,8 +10,8 @@ use std::process::exit;
use std::str::FromStr;
use std::time::Duration;
use base64::Engine;
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use chrono::Utc;
use fern::colors::ColoredLevelConfig;
use log::{debug, error, info, warn};
@@ -59,9 +59,9 @@ use yaak_models::queries::{
};
use yaak_plugin_runtime::events::{
BootResponse, CallHttpRequestActionRequest, FilterResponse, FindHttpResponsesResponse,
GetHttpRequestActionsResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse,
GetHttpRequestActionsResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, Icon,
InternalEvent, InternalEventPayload, RenderHttpRequestResponse, SendHttpRequestResponse,
ShowToastRequest, ToastVariant,
ShowToastRequest,
};
use yaak_plugin_runtime::plugin_handle::PluginHandle;
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(
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
message: format!("Reloaded plugin {}", plugin_handle.dir),
variant: ToastVariant::Info,
icon: Some(Icon::Info),
..Default::default()
}),
None,
);
@@ -2223,9 +2224,9 @@ fn get_focused_window_no_lock<R: Runtime>(app_handle: &AppHandle<R>) -> Option<W
}
})
.collect::<Vec<WebviewWindow<R>>>();
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

View File

@@ -169,27 +169,43 @@ pub struct RenderHttpRequestResponse {
#[ts(export, export_to="events.ts")]
pub struct ShowToastRequest {
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)]
#[serde(rename_all = "snake_case")]
#[ts(export, export_to="events.ts")]
pub enum ToastVariant {
pub enum Color {
Custom,
Copied,
Success,
Default,
Primary,
Secondary,
Info,
Success,
Notice,
Warning,
Error,
Danger,
}
impl Default for ToastVariant {
impl Default for Color {
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)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to="events.ts")]

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ import { useGrpcEvents } from '../hooks/useGrpcEvents';
import { usePinnedGrpcConnection } from '../hooks/usePinnedGrpcConnection';
import { useStateWithDeps } from '../hooks/useStateWithDeps';
import type { GrpcEvent, GrpcRequest } from '@yaakapp/api';
import { isResponseLoading } from '../lib/models';
import { isResponseLoading } from '../lib/model_util';
import { Banner } from './core/Banner';
import { Button } from './core/Button';
import { Icon } from './core/Icon';
@@ -197,34 +197,34 @@ function EventRow({
eventType === 'server_message'
? 'text-info'
: eventType === 'client_message'
? 'text-primary'
: eventType === 'error' || (status != null && status > 0)
? 'text-danger'
: eventType === 'connection_end'
? 'text-success'
: 'text-text-subtle'
? 'text-primary'
: eventType === 'error' || (status != null && status > 0)
? 'text-danger'
: eventType === 'connection_end'
? 'text-success'
: 'text-text-subtle'
}
title={
eventType === 'server_message'
? 'Server message'
: eventType === 'client_message'
? 'Client message'
: eventType === 'error' || (status != null && status > 0)
? 'Error'
: eventType === 'connection_end'
? 'Connection response'
: undefined
? 'Client message'
: eventType === 'error' || (status != null && status > 0)
? 'Error'
: eventType === 'connection_end'
? 'Connection response'
: undefined
}
icon={
eventType === 'server_message'
? 'arrowBigDownDash'
? 'arrow_big_down_dash'
: eventType === 'client_message'
? 'arrowBigUpDash'
: eventType === 'error' || (status != null && status > 0)
? 'alert'
: eventType === 'connection_end'
? 'check'
: 'info'
? 'arrow_big_up_dash'
: eventType === 'error' || (status != null && status > 0)
? 'alert_triangle'
: eventType === 'connection_end'
? 'check'
: 'info'
}
/>
<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 { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
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 { BearerAuth } from './BearerAuth';
import { Button } from './core/Button';
@@ -218,7 +218,7 @@ export function GrpcConnectionSetupPane({
<Button
size="sm"
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}
className={classNames(
'font-mono text-editor min-w-[5rem] !ring-0',
@@ -254,7 +254,7 @@ export function GrpcConnectionSetupPane({
title={isStreaming ? 'Connect' : 'Send'}
hotkeyAction="grpc_request.send"
onClick={isStreaming ? handleSend : handleConnect}
icon={isStreaming ? 'sendHorizontal' : 'arrowUpDown'}
icon={isStreaming ? 'send_horizontal' : 'arrow_up_down'}
/>
</>
) : (
@@ -269,8 +269,8 @@ export function GrpcConnectionSetupPane({
isStreaming
? 'x'
: methodType.includes('streaming')
? 'arrowUpDown'
: 'sendHorizontal'
? 'arrow_up_down'
: 'send_horizontal'
}
/>
)}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -64,13 +64,13 @@ export function SettingsDropdown() {
{
key: 'import-data',
label: 'Import Data',
leftSlot: <Icon icon="folderInput" />,
leftSlot: <Icon icon="folder_input" />,
onSelect: () => importData.mutate(),
},
{
key: 'export-data',
label: 'Export Data',
leftSlot: <Icon icon="folderOutput" />,
leftSlot: <Icon icon="folder_output" />,
onSelect: () => exportData.mutate(),
},
{ type: 'separator', label: `Yaak v${appInfo.version}` },
@@ -84,14 +84,14 @@ export function SettingsDropdown() {
key: 'feedback',
label: 'Feedback',
leftSlot: <Icon icon="chat" />,
rightSlot: <Icon icon="externalLink" />,
rightSlot: <Icon icon="external_link" />,
onSelect: () => open('https://yaak.app/roadmap'),
},
{
key: 'changelog',
label: 'Changelog',
leftSlot: <Icon icon="cake" />,
rightSlot: <Icon icon="externalLink" />,
rightSlot: <Icon icon="external_link" />,
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 { useWorkspaces } from '../hooks/useWorkspaces';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { isResponseLoading } from '../lib/models';
import { isResponseLoading } from '../lib/model_util';
import { getHttpRequest } from '../lib/store';
import type { DropdownItem } from './core/Dropdown';
import { ContextMenu } from './core/Dropdown';
@@ -433,32 +433,31 @@ export function Sidebar({ className }: Props) {
onBlur={handleBlur}
tabIndex={hidden ? -1 : 0}
onContextMenu={handleMainContextMenu}
className={classNames(
className,
'h-full pb-3 overflow-y-scroll overflow-x-visible hide-scrollbars pt-2',
)}
className={classNames(className, 'h-full grid grid-rows-[minmax(0,1fr)_auto]')}
>
<ContextMenu
triggerPosition={showMainContextMenu}
items={mainContextMenuItems}
onClose={() => setShowMainContextMenu(null)}
/>
<SidebarItems
treeParentMap={treeParentMap}
activeId={activeRequest?.id ?? null}
selectedId={selectedId}
selectedTree={selectedTree}
isCollapsed={isCollapsed}
tree={tree}
focused={hasFocus}
draggingId={draggingId}
onSelect={handleSelect}
hoveredIndex={hoveredIndex}
hoveredTree={hoveredTree}
handleMove={handleMove}
handleEnd={handleEnd}
handleDragStart={handleDragStart}
/>
<div className="pb-3 overflow-x-visible overflow-y-scroll pt-2">
<ContextMenu
triggerPosition={showMainContextMenu}
items={mainContextMenuItems}
onClose={() => setShowMainContextMenu(null)}
/>
<SidebarItems
treeParentMap={treeParentMap}
activeId={activeRequest?.id ?? null}
selectedId={selectedId}
selectedTree={selectedTree}
isCollapsed={isCollapsed}
tree={tree}
focused={hasFocus}
draggingId={draggingId}
onSelect={handleSelect}
hoveredIndex={hoveredIndex}
hoveredTree={hoveredTree}
handleMove={handleMove}
handleEnd={handleEnd}
handleDragStart={handleDragStart}
/>
</div>
</aside>
);
}
@@ -741,7 +740,7 @@ function SidebarItem({
{
key: 'sendAll',
label: 'Send All',
leftSlot: <Icon icon="sendHorizontal" />,
leftSlot: <Icon icon="send_horizontal" />,
onSelect: () => sendManyRequests.mutate(child.children.map((c) => c.item.id)),
},
{
@@ -784,7 +783,7 @@ function SidebarItem({
label: 'Send',
hotKeyAction: 'http_request.send',
hotKeyLabelOnly: true, // Already bound in URL bar
leftSlot: <Icon icon="sendHorizontal" />,
leftSlot: <Icon icon="send_horizontal" />,
onSelect: () => sendRequest.mutate(itemId),
},
...httpRequestActions.map((a) => ({
@@ -822,7 +821,7 @@ function SidebarItem({
{
key: 'moveWorkspace',
label: 'Move',
leftSlot: <Icon icon="arrowRightCircle" />,
leftSlot: <Icon icon="arrow_right_circle" />,
hidden: workspaces.length <= 1,
onSelect: moveToWorkspace.mutate,
},
@@ -882,7 +881,7 @@ function SidebarItem({
{itemModel === 'folder' && (
<Icon
size="sm"
icon="chevronRight"
icon="chevron_right"
className={classNames(
'text-text-subtlest',
'transition-transform',

View File

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

View File

@@ -1,12 +1,12 @@
import { AnimatePresence } from 'framer-motion';
import type { ReactNode } 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 { generateId } from '../lib/generateId';
import type { ToastProps } from './core/Toast';
import { Toast } from './core/Toast';
import { generateId } from '../lib/generateId';
import { Portal } from './Portal';
import { AnimatePresence } from 'framer-motion';
type ToastEntry = {
id?: string;
@@ -93,7 +93,7 @@ export const Toasts = () => {
const { toasts } = useContext(ToastContext);
return (
<Portal name="toasts">
<div className="absolute right-0 bottom-0 z-10">
<div className="absolute right-0 bottom-0 z-20">
<AnimatePresence>
{toasts.map((props: PrivateToastEntry) => (
<ToastInstance key={props.id} {...props} />

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ export function Banner({ children, className, color = 'secondary' }: Props) {
className={classNames(
className,
`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',
'overflow-x-auto text-text',
)}

View File

@@ -108,6 +108,11 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
className={classes}
disabled={disabled || isLoading}
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}
{...props}
>
@@ -126,7 +131,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
{children}
</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>
);
});

View File

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

View File

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

View File

@@ -4,62 +4,69 @@ import type { HTMLAttributes } from 'react';
import { memo } from 'react';
const icons = {
alert: lucide.AlertTriangleIcon,
alert_triangle: lucide.AlertTriangleIcon,
archive: lucide.ArchiveIcon,
arrowBigDownDash: lucide.ArrowBigDownDashIcon,
arrowRightCircle: lucide.ArrowRightCircleIcon,
arrowBigLeftDash: lucide.ArrowBigLeftDashIcon,
arrowBigRight: lucide.ArrowBigRightIcon,
arrowBigRightDash: lucide.ArrowBigRightDashIcon,
arrowBigUpDash: lucide.ArrowBigUpDashIcon,
arrowDown: lucide.ArrowDownIcon,
arrowDownToDot: lucide.ArrowDownToDotIcon,
arrowUp: lucide.ArrowUpIcon,
arrowUpDown: lucide.ArrowUpDownIcon,
arrowUpFromDot: lucide.ArrowUpFromDotIcon,
arrow_big_down_dash: lucide.ArrowBigDownDashIcon,
arrow_right_circle: lucide.ArrowRightCircleIcon,
arrow_big_left_dash: lucide.ArrowBigLeftDashIcon,
arrow_big_right: lucide.ArrowBigRightIcon,
arrow_big_right_dash: lucide.ArrowBigRightDashIcon,
arrow_big_up_dash: lucide.ArrowBigUpDashIcon,
arrow_down: lucide.ArrowDownIcon,
arrow_down_to_dot: lucide.ArrowDownToDotIcon,
arrow_up: lucide.ArrowUpIcon,
arrow_up_down: lucide.ArrowUpDownIcon,
arrow_up_from_dot: lucide.ArrowUpFromDotIcon,
badge_check: lucide.BadgeCheckIcon,
box: lucide.BoxIcon,
cake: lucide.CakeIcon,
chat: lucide.MessageSquare,
check: lucide.CheckIcon,
checkCircle: lucide.CheckCircleIcon,
chevronDown: lucide.ChevronDownIcon,
chevronRight: lucide.ChevronRightIcon,
check_circle: lucide.CheckCircleIcon,
chevron_down: lucide.ChevronDownIcon,
chevron_right: lucide.ChevronRightIcon,
circle_alert: lucide.CircleAlertIcon,
cloud: lucide.CloudIcon,
code: lucide.CodeIcon,
cookie: lucide.CookieIcon,
copy: lucide.CopyIcon,
copyCheck: lucide.CopyCheck,
copy_check: lucide.CopyCheck,
download: lucide.DownloadIcon,
externalLink: lucide.ExternalLinkIcon,
external_link: lucide.ExternalLinkIcon,
eye: lucide.EyeIcon,
eyeClosed: lucide.EyeOffIcon,
fileCode: lucide.FileCodeIcon,
eye_closed: lucide.EyeOffIcon,
file_code: lucide.FileCodeIcon,
filter: lucide.FilterIcon,
flask: lucide.FlaskConicalIcon,
folderInput: lucide.FolderInputIcon,
folderOutput: lucide.FolderOutputIcon,
gripVertical: lucide.GripVerticalIcon,
folder_input: lucide.FolderInputIcon,
folder_output: lucide.FolderOutputIcon,
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,
help: lucide.CircleHelpIcon,
house: lucide.HomeIcon,
info: lucide.InfoIcon,
keyboard: lucide.KeyboardIcon,
leftPanelHidden: lucide.PanelLeftOpenIcon,
leftPanelVisible: lucide.PanelLeftCloseIcon,
magicWand: lucide.Wand2Icon,
left_panel_hidden: lucide.PanelLeftOpenIcon,
left_panel_visible: lucide.PanelLeftCloseIcon,
magic_wand: lucide.Wand2Icon,
minus: lucide.MinusIcon,
moon: lucide.MoonIcon,
moreVertical: lucide.MoreVerticalIcon,
more_vertical: lucide.MoreVerticalIcon,
paste: lucide.ClipboardPasteIcon,
pencil: lucide.PencilIcon,
pin: lucide.PinIcon,
plug: lucide.Plug,
plus: lucide.PlusIcon,
plusCircle: lucide.PlusCircleIcon,
plus_circle: lucide.PlusCircleIcon,
refresh: lucide.RefreshCwIcon,
save: lucide.SaveIcon,
search: lucide.SearchIcon,
sendHorizontal: lucide.SendHorizonalIcon,
settings2: lucide.Settings2Icon,
send_horizontal: lucide.SendHorizonalIcon,
settings: lucide.SettingsIcon,
sparkles: lucide.SparklesIcon,
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"
iconClassName="text-text-subtle group-hover/obscure:text"
iconSize="sm"
icon={obscured ? 'eye' : 'eyeClosed'}
icon={obscured ? 'eye' : 'eye_closed'}
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}>
<Icon
size="xs"
icon="chevronRight"
icon="chevron_right"
className={classNames(
'left-0 absolute transition-transform flex items-center',
'text-text-subtlest group-hover:text-text-subtle',

View File

@@ -22,7 +22,7 @@ export function Link({ href, children, className, ...other }: Props) {
{...other}
>
<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>
);
}

View File

@@ -397,7 +397,7 @@ function PairEditorRow({
'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>
) : (
<span className="w-3" />
@@ -545,7 +545,7 @@ function PairEditorRow({
<IconButton
iconSize="sm"
size="xs"
icon={isLast ? 'empty' : 'chevronDown'}
icon={isLast ? 'empty' : 'chevron_down'}
title="Select form data type"
/>
</RadioDropdown>
@@ -563,7 +563,7 @@ function PairEditorRow({
<IconButton
iconSize="sm"
size="xs"
icon={isLast ? 'empty' : 'chevronDown'}
icon={isLast ? 'empty' : 'chevron_down'}
title="Select form data type"
/>
</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',
)}
onClick={() => setUseBulk((b) => !b)}
icon={useBulk ? 'table' : 'fileCode'}
icon={useBulk ? 'table' : 'file_code'}
/>
</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"
iconClassName="text-text-subtle group-hover/obscure:text"
iconSize="sm"
icon={obscured ? 'eye' : 'eyeClosed'}
icon={obscured ? 'eye' : 'eye_closed'}
onClick={() => setObscured((o) => !o)}
/>
)}

View File

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

View File

@@ -1,3 +1,4 @@
import type { ShowToastRequest } from '@yaakapp/api';
import classNames from 'classnames';
import { motion } from 'framer-motion';
import type { ReactNode } from 'react';
@@ -15,27 +16,23 @@ export interface ToastProps {
className?: string;
timeout: number | null;
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,
copied: 'copyCheck',
warning: 'alert',
error: 'alert',
default: 'info',
danger: 'alert_triangle',
info: 'info',
success: 'checkCircle',
notice: 'alert_triangle',
primary: 'info',
secondary: 'info',
success: 'check_circle',
warning: 'alert_triangle',
};
export function Toast({
children,
className,
open,
onClose,
timeout,
action,
variant = 'info',
}: ToastProps) {
export function Toast({ children, open, onClose, timeout, action, icon, color }: ToastProps) {
useKey(
'Escape',
() => {
@@ -45,8 +42,9 @@ export function Toast({
{},
[open],
);
color = color ?? 'default';
const icon = variant in ICONS && ICONS[variant];
const toastIcon = icon ?? (color in ICONS && ICONS[color]);
return (
<motion.div
@@ -54,55 +52,46 @@ export function Toast({
animate={{ opacity: 100, right: 0 }}
exit={{ opacity: 0, right: '-100%' }}
transition={{ duration: 0.2 }}
className={classNames(
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',
)}
className={classNames('bg-surface m-2 rounded-lg')}
>
<div className="px-3 py-3 flex items-center gap-2">
{icon && (
<Icon
icon={icon}
className={classNames(
variant === 'success' && 'text-success',
variant === 'warning' && 'text-warning',
variant === 'error' && 'text-danger',
variant === 'copied' && 'text-primary',
)}
/>
<div
className={classNames(
`x-theme-toast x-theme-toast--${color}`,
'pointer-events-auto overflow-hidden',
'relative pointer-events-auto bg-surface text-text rounded-lg',
'border border-border shadow-lg',
'grid grid-cols-[1fr_auto]',
'text',
)}
<VStack space={2}>
<div>{children}</div>
{action}
</VStack>
</div>
<IconButton
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 className="px-3 py-3 flex items-center gap-2">
{toastIcon && <Icon icon={toastIcon} className="text-text-subtle" />}
<VStack space={2}>
<div>{children}</div>
{action}
</VStack>
</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>
);
}

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import { useMemo } from 'react';
import type { DropdownItem } from '../components/core/Dropdown';
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 { useCreateGrpcRequest } from './useCreateGrpcRequest';
import { useCreateHttpRequest } from './useCreateHttpRequest';

View File

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

View File

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

View File

@@ -51,7 +51,7 @@ export function useImportQuerystring(requestId: string) {
if (additionalUrlParameters.length > 0) {
toast.show({
id: 'querystring-imported',
variant: 'info',
color: 'info',
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';
export function useIsResponseLoading(requestId: string | null): boolean {

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import { readFile } from '@tauri-apps/plugin-fs';
import type { HttpResponse } from '@yaakapp/api';
import { getCharsetFromContentType } from './models';
import { getCharsetFromContentType } from './model_util';
export async function getResponseBodyText(response: HttpResponse): Promise<string | null> {
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> {
return {
text: color.lift(0.8),
textSubtle: color.translucify(0.3),
textSubtlest: color,
surface: color,
surface: color.translucify(0.9),
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 {
const yaakColor = colors?.[color];
if (yaakColor == null) {
@@ -253,6 +271,9 @@ export function getThemeCSS(theme: YaakTheme): string {
...Object.keys(colors ?? {}).map((key) =>
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) =>
templateTagCSS(key as YaakColorKey, theme.components?.templateTag ?? colors),
),