Tweak settings for release

This commit is contained in:
Gregory Schier
2024-05-30 10:28:59 -07:00
parent 90637fda6b
commit 2caa735a2e
20 changed files with 191 additions and 227 deletions

View File

@@ -23,7 +23,7 @@ use sqlx::{Pool, Sqlite, SqlitePool};
use sqlx::migrate::Migrator; use sqlx::migrate::Migrator;
use sqlx::sqlite::SqliteConnectOptions; use sqlx::sqlite::SqliteConnectOptions;
use sqlx::types::Json; use sqlx::types::Json;
use tauri::{AppHandle, RunEvent, State, WebviewUrl, WebviewWindow}; use tauri::{AppHandle, LogicalSize, RunEvent, State, WebviewUrl, WebviewWindow};
use tauri::{Manager, WindowEvent}; use tauri::{Manager, WindowEvent};
use tauri::path::BaseDirectory; use tauri::path::BaseDirectory;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@@ -74,6 +74,9 @@ mod tauri_plugin_mac_window;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod tauri_plugin_windows_window; mod tauri_plugin_windows_window;
const DEFAULT_WINDOW_WIDTH: i32 = 1100;
const DEFAULT_WINDOW_HEIGHT: i32 = 600;
async fn migrate_db(app_handle: &AppHandle, db: &Mutex<Pool<Sqlite>>) -> Result<(), String> { async fn migrate_db(app_handle: &AppHandle, db: &Mutex<Pool<Sqlite>>) -> Result<(), String> {
let pool = &*db.lock().await; let pool = &*db.lock().await;
let p = app_handle let p = app_handle
@@ -1784,41 +1787,6 @@ fn create_nested_window(window: &WebviewWindow, label: &str, url: &str, title: &
let win = win_builder.build().expect("failed to build window"); let win = win_builder.build().expect("failed to build window");
// Tauri doesn't support shadows when hiding decorations, so we add our own
// #[cfg(any(windows, target_os = "macos"))]
// set_shadow(&win, true).unwrap();
let win2 = win.clone();
win.on_menu_event(move |w, event| {
if !w.is_focused().unwrap() {
return;
}
match event.id().0.as_str() {
"quit" => exit(0),
"close" => _ = w.close(),
"zoom_reset" => w.emit("zoom_reset", true).unwrap(),
"zoom_in" => w.emit("zoom_in", true).unwrap(),
"zoom_out" => w.emit("zoom_out", true).unwrap(),
"settings" => w.emit("settings", true).unwrap(),
"refresh" => win2.eval("location.reload()").unwrap(),
"open_feedback" => {
_ = win2
.app_handle()
.shell()
.open("https://yaak.canny.io", None)
}
"toggle_devtools" => {
if win2.is_devtools_open() {
win2.close_devtools();
} else {
win2.open_devtools();
}
}
_ => {}
}
});
win win
} }
@@ -1840,7 +1808,7 @@ fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
.resizable(true) .resizable(true)
.fullscreen(false) .fullscreen(false)
.disable_drag_drop_handler() // Required for frontend Dnd on windows .disable_drag_drop_handler() // Required for frontend Dnd on windows
.inner_size(1100.0, 600.0) .inner_size(DEFAULT_WINDOW_WIDTH as f64, DEFAULT_WINDOW_HEIGHT as f64)
.position( .position(
// Randomly offset so windows don't stack exactly // Randomly offset so windows don't stack exactly
100.0 + random::<f64>() * 30.0, 100.0 + random::<f64>() * 30.0,
@@ -1866,7 +1834,7 @@ fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
let win = win_builder.build().expect("failed to build window"); let win = win_builder.build().expect("failed to build window");
let win2 = win.clone(); let webview_window = win.clone();
win.on_menu_event(move |w, event| { win.on_menu_event(move |w, event| {
if !w.is_focused().unwrap() { if !w.is_focused().unwrap() {
return; return;
@@ -1874,23 +1842,26 @@ fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
match event.id().0.as_str() { match event.id().0.as_str() {
"quit" => exit(0), "quit" => exit(0),
"close" => _ = w.close(), "close" => w.close().unwrap(),
"zoom_reset" => w.emit("zoom_reset", true).unwrap(), "zoom_reset" => w.emit("zoom_reset", true).unwrap(),
"zoom_in" => w.emit("zoom_in", true).unwrap(), "zoom_in" => w.emit("zoom_in", true).unwrap(),
"zoom_out" => w.emit("zoom_out", true).unwrap(), "zoom_out" => w.emit("zoom_out", true).unwrap(),
"settings" => w.emit("settings", true).unwrap(), "settings" => w.emit("settings", true).unwrap(),
"refresh" => win2.eval("location.reload()").unwrap(),
"open_feedback" => { "open_feedback" => {
_ = win2 _ = webview_window
.app_handle() .app_handle()
.shell() .shell()
.open("https://yaak.canny.io", None) .open("https://yaak.canny.io", None)
} }
"toggle_devtools" => {
if win2.is_devtools_open() { // Commands for development
win2.close_devtools(); "dev.reset_size" => webview_window.set_size(LogicalSize::new(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT)).unwrap(),
"dev.refresh" => webview_window.eval("location.reload()").unwrap(),
"dev.toggle_devtools" => {
if webview_window.is_devtools_open() {
webview_window.close_devtools();
} else { } else {
win2.open_devtools(); webview_window.open_devtools();
} }
} }
_ => {} _ => {}

View File

@@ -126,12 +126,14 @@ pub fn app_menu(app_handle: &AppHandle) -> tauri::Result<Menu<Wry>> {
"Develop", "Develop",
true, true,
&[ &[
&MenuItemBuilder::with_id("refresh".to_string(), "Refresh") &MenuItemBuilder::with_id("dev.refresh".to_string(), "Refresh")
.accelerator("CmdOrCtrl+Shift+r") .accelerator("CmdOrCtrl+Shift+r")
.build(app_handle)?, .build(app_handle)?,
&MenuItemBuilder::with_id("toggle_devtools".to_string(), "Open Devtools") &MenuItemBuilder::with_id("dev.toggle_devtools".to_string(), "Open Devtools")
.accelerator("CmdOrCtrl+Option+i") .accelerator("CmdOrCtrl+Option+i")
.build(app_handle)?, .build(app_handle)?,
&MenuItemBuilder::with_id("dev.reset_size".to_string(), "Reset Size")
.build(app_handle)?,
], ],
)?, )?,
], ],

View File

@@ -3,7 +3,7 @@ import { routePaths, useAppRoutes } from '../hooks/useAppRoutes';
import { DefaultLayout } from './DefaultLayout'; import { DefaultLayout } from './DefaultLayout';
import { RedirectToLatestWorkspace } from './RedirectToLatestWorkspace'; import { RedirectToLatestWorkspace } from './RedirectToLatestWorkspace';
import RouteError from './RouteError'; import RouteError from './RouteError';
import { SettingsDialog } from './Settings/SettingsDialog'; import { Settings } from './Settings/Settings';
import Workspace from './Workspace'; import Workspace from './Workspace';
const router = createBrowserRouter([ const router = createBrowserRouter([
@@ -41,7 +41,7 @@ const router = createBrowserRouter([
path: routePaths.workspaceSettings({ path: routePaths.workspaceSettings({
workspaceId: ':workspaceId', workspaceId: ':workspaceId',
}), }),
element: <SettingsDialog fullscreen />, element: <Settings />,
}, },
], ],
}, },

View File

@@ -20,7 +20,6 @@ import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey'; import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { settingsQueryKey, useSettings } from '../hooks/useSettings'; import { settingsQueryKey, useSettings } from '../hooks/useSettings';
import { useSyncThemeToDocument } from '../hooks/useSyncThemeToDocument'; import { useSyncThemeToDocument } from '../hooks/useSyncThemeToDocument';
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
import { workspacesQueryKey } from '../hooks/useWorkspaces'; import { workspacesQueryKey } from '../hooks/useWorkspaces';
import { useZoom } from '../hooks/useZoom'; import { useZoom } from '../hooks/useZoom';
import type { Model } from '../lib/models'; import type { Model } from '../lib/models';
@@ -34,7 +33,6 @@ export function GlobalHooks() {
// Other useful things // Other useful things
useSyncThemeToDocument(); useSyncThemeToDocument();
useSyncWindowTitle();
useGlobalCommands(); useGlobalCommands();
useCommandPalette(); useCommandPalette();
useNotificationToast(); useNotificationToast();

View File

@@ -0,0 +1,47 @@
import { getCurrent } from '@tauri-apps/api/webviewWindow';
import { createGlobalState, useKeyPressEvent } from 'react-use';
import { capitalize } from '../../lib/capitalize';
import { TabContent, Tabs } from '../core/Tabs/Tabs';
import { SettingsAppearance } from './SettingsAppearance';
import { SettingsGeneral } from './SettingsGeneral';
enum Tab {
General = 'general',
Appearance = 'appearance',
}
const tabs = [Tab.General, Tab.Appearance];
const useTabState = createGlobalState<string>(tabs[0]!);
export const Settings = () => {
const [tab, setTab] = useTabState();
// Close settings window on escape
// TODO: Could this be put in a better place? Eg. in Rust key listener when creating the window
useKeyPressEvent('Escape', () => getCurrent().close());
return (
<>
<div
data-tauri-drag-region
className="h-[27px] bg-background-highlight-secondary flex items-center justify-center border-b border-background-highlight"
>
Settings
</div>
<Tabs
value={tab}
addBorders
label="Settings"
onChangeValue={setTab}
tabs={tabs.map((value) => ({ value, label: capitalize(value) }))}
>
<TabContent value={Tab.General} className="pt-3 overflow-y-auto h-full px-4">
<SettingsGeneral />
</TabContent>
<TabContent value={Tab.Appearance} className="pt-3 overflow-y-auto h-full px-4">
<SettingsAppearance />
</TabContent>
</Tabs>
</>
);
};

View File

@@ -5,19 +5,23 @@ import { useResolvedTheme } from '../../hooks/useResolvedTheme';
import { useSettings } from '../../hooks/useSettings'; import { useSettings } from '../../hooks/useSettings';
import { useThemes } from '../../hooks/useThemes'; import { useThemes } from '../../hooks/useThemes';
import { useUpdateSettings } from '../../hooks/useUpdateSettings'; import { useUpdateSettings } from '../../hooks/useUpdateSettings';
import { trackEvent } from '../../lib/analytics';
import { clamp } from '../../lib/clamp'; import { clamp } from '../../lib/clamp';
import { isThemeDark } from '../../lib/theme/window'; import { isThemeDark } from '../../lib/theme/window';
import type { ButtonProps } from '../core/Button'; import type { ButtonProps } from '../core/Button';
import { Checkbox } from '../core/Checkbox';
import { Editor } from '../core/Editor'; import { Editor } from '../core/Editor';
import type { IconProps } from '../core/Icon'; import type { IconProps } from '../core/Icon';
import { Icon } from '../core/Icon';
import { IconButton } from '../core/IconButton'; import { IconButton } from '../core/IconButton';
import { PlainInput } from '../core/PlainInput';
import type { SelectOption } from '../core/Select'; import type { SelectOption } from '../core/Select';
import { Select } from '../core/Select'; import { Select } from '../core/Select';
import { Separator } from '../core/Separator'; import { Separator } from '../core/Separator';
import { HStack, VStack } from '../core/Stacks'; import { HStack, VStack } from '../core/Stacks';
const fontSizes = [
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
].map((n) => ({ label: `${n}`, value: `${n}` }));
const buttonColors: ButtonProps['color'][] = [ const buttonColors: ButtonProps['color'][] = [
'primary', 'primary',
'info', 'info',
@@ -75,39 +79,28 @@ export function SettingsAppearance() {
return ( return (
<VStack space={2} className="mb-4"> <VStack space={2} className="mb-4">
<PlainInput <Select
size="sm" size="sm"
name="interfaceFontSize" name="interfaceFontSize"
label="Font Size" label="Font Size"
placeholder="16"
step={0.5}
type="number"
labelPosition="left" labelPosition="left"
defaultValue={`${settings.interfaceFontSize}`} value={`${settings.interfaceFontSize}`}
validate={(value) => parseInt(value) >= 8 && parseInt(value) <= 30} options={fontSizes}
onChange={(v) => onChange={(v) => updateSettings.mutate({ interfaceFontSize: parseInt(v) })}
updateSettings.mutate({
...settings,
interfaceFontSize: clamp(parseInt(v) || 16, 8, 30),
})
}
/> />
<PlainInput <Select
size="sm" size="sm"
name="editorFontSize" name="editorFontSize"
label="Editor Font Size" label="Editor Font Size"
placeholder="14"
step={0.5}
type="number"
labelPosition="left" labelPosition="left"
defaultValue={`${settings.editorFontSize}`} value={`${settings.editorFontSize}`}
validate={(value) => parseInt(value) >= 8 && parseInt(value) <= 30} options={fontSizes}
onChange={(v) => onChange={(v) => updateSettings.mutate({ editorFontSize: clamp(parseInt(v) || 14, 8, 30) })}
updateSettings.mutate({ />
...settings, <Checkbox
editorFontSize: clamp(parseInt(v) || 14, 8, 30), checked={settings.editorSoftWrap}
}) title="Wrap Editor Lines"
} onChange={(editorSoftWrap) => updateSettings.mutate({ editorSoftWrap })}
/> />
<Separator className="my-4" /> <Separator className="my-4" />
@@ -118,9 +111,8 @@ export function SettingsAppearance() {
labelPosition="left" labelPosition="left"
size="sm" size="sm"
value={settings.appearance} value={settings.appearance}
onChange={async (appearance) => { onChange={(appearance) => {
await updateSettings.mutateAsync({ ...settings, appearance }); updateSettings.mutateAsync({ appearance });
trackEvent('setting', 'update', { appearance });
}} }}
options={[ options={[
{ label: 'Automatic', value: 'system' }, { label: 'Automatic', value: 'system' },
@@ -128,42 +120,41 @@ export function SettingsAppearance() {
{ label: 'Dark', value: 'dark' }, { label: 'Dark', value: 'dark' },
]} ]}
/> />
{(settings.appearance === 'system' || settings.appearance === 'light') && ( <HStack space={2}>
<Select {(settings.appearance === 'system' || settings.appearance === 'light') && (
name="lightTheme" <Select
label={settings.appearance === 'system' ? 'Light Theme' : 'Theme'} hideLabel
labelPosition="left" leftSlot={<Icon icon="sun" />}
size="sm" name="lightTheme"
value={activeTheme.light.id} label="Light Theme"
options={lightThemes} size="sm"
onChange={async (themeLight) => { value={activeTheme.light.id}
await updateSettings.mutateAsync({ ...settings, themeLight }); options={lightThemes}
trackEvent('setting', 'update', { themeLight }); onChange={(themeLight) => updateSettings.mutateAsync({ ...settings, themeLight })}
}} />
/> )}
)} {(settings.appearance === 'system' || settings.appearance === 'dark') && (
{(settings.appearance === 'system' || settings.appearance === 'dark') && ( <Select
<Select hideLabel
name="darkTheme" name="darkTheme"
label={settings.appearance === 'system' ? 'Dark Theme' : 'Theme'} label="Dark Theme"
labelPosition="left" leftSlot={<Icon icon="moon" />}
size="sm" size="sm"
value={activeTheme.dark.id} value={activeTheme.dark.id}
options={darkThemes} options={darkThemes}
onChange={async (themeDark) => { onChange={(themeDark) => updateSettings.mutateAsync({ ...settings, themeDark })}
await updateSettings.mutateAsync({ ...settings, themeDark }); />
trackEvent('setting', 'update', { themeDark }); )}
}} </HStack>
/>
)}
<VStack <VStack
space={3} space={3}
className="mt-3 w-full bg-background p-3 border border-dashed border-background-highlight rounded overflow-x-auto" className="mt-3 w-full bg-background p-3 border border-dashed border-background-highlight rounded overflow-x-auto"
> >
<div className="text-fg font-bold"> <HStack className="text-fg font-bold" alignItems="center" space={2}>
Theme Preview <span className="text-fg-subtle">({appearance})</span> Theme Preview{' '}
</div> <Icon icon={appearance === 'dark' ? 'moon' : 'sun'} className="text-fg-subtle" />
</HStack>
<HStack space={1.5} alignItems="center" className="w-full"> <HStack space={1.5} alignItems="center" className="w-full">
{buttonColors.map((c, i) => ( {buttonColors.map((c, i) => (
<IconButton <IconButton

View File

@@ -1,61 +0,0 @@
import classNames from 'classnames';
import { createGlobalState } from 'react-use';
import { useAppInfo } from '../../hooks/useAppInfo';
import { capitalize } from '../../lib/capitalize';
import { TabContent, Tabs } from '../core/Tabs/Tabs';
import { SettingsAppearance } from './SettingsAppearance';
import { SettingsDesign } from './SettingsDesign';
import { SettingsGeneral } from './SettingsGeneral';
enum Tab {
General = 'general',
Appearance = 'appearance',
// Dev-only
Design = 'design',
}
const tabs = [Tab.General, Tab.Appearance, Tab.Design];
const useTabState = createGlobalState<string>(tabs[0]!);
interface Props {
fullscreen?: true;
}
export const SettingsDialog = ({ fullscreen }: Props) => {
const [tab, setTab] = useTabState();
const appInfo = useAppInfo();
const isDev = appInfo?.isDev ?? false;
return (
<div className={classNames(!fullscreen && 'w-[70vw] max-w-[40rem] h-[80vh]')}>
{fullscreen && (
<div
data-tauri-drag-region
className="h-[27px] bg-background-highlight-secondary flex items-center justify-center border-b border-background-highlight"
>
Settings
</div>
)}
<Tabs
value={tab}
addBorders
label="Settings"
onChangeValue={setTab}
tabs={tabs
.filter((t) => t !== Tab.Design || isDev)
.map((value) => ({ value, label: capitalize(value) }))}
>
<TabContent value={Tab.General} className="pt-3 overflow-y-auto h-full px-4">
<SettingsGeneral />
</TabContent>
<TabContent value={Tab.Appearance} className="pt-3 overflow-y-auto h-full px-4">
<SettingsAppearance />
</TabContent>
<TabContent value={Tab.Design} className="pt-3 overflow-y-auto h-full px-4">
<SettingsDesign />
</TabContent>
</Tabs>
</div>
);
};

View File

@@ -5,7 +5,6 @@ import { useCheckForUpdates } from '../../hooks/useCheckForUpdates';
import { useSettings } from '../../hooks/useSettings'; import { useSettings } from '../../hooks/useSettings';
import { useUpdateSettings } from '../../hooks/useUpdateSettings'; import { useUpdateSettings } from '../../hooks/useUpdateSettings';
import { useUpdateWorkspace } from '../../hooks/useUpdateWorkspace'; import { useUpdateWorkspace } from '../../hooks/useUpdateWorkspace';
import { trackEvent } from '../../lib/analytics';
import { Checkbox } from '../core/Checkbox'; import { Checkbox } from '../core/Checkbox';
import { Heading } from '../core/Heading'; import { Heading } from '../core/Heading';
import { IconButton } from '../core/IconButton'; import { IconButton } from '../core/IconButton';
@@ -36,10 +35,7 @@ export function SettingsGeneral() {
labelPosition="left" labelPosition="left"
size="sm" size="sm"
value={settings.updateChannel} value={settings.updateChannel}
onChange={(updateChannel) => { onChange={(updateChannel) => updateSettings.mutate({ updateChannel })}
trackEvent('setting', 'update', { update_channel: updateChannel });
updateSettings.mutate({ ...settings, updateChannel });
}}
options={[ options={[
{ label: 'Release', value: 'stable' }, { label: 'Release', value: 'stable' },
{ label: 'Early Bird (Beta)', value: 'beta' }, { label: 'Early Bird (Beta)', value: 'beta' },
@@ -77,23 +73,15 @@ export function SettingsGeneral() {
<Checkbox <Checkbox
checked={workspace.settingValidateCertificates} checked={workspace.settingValidateCertificates}
title="Validate TLS Certificates" title="Validate TLS Certificates"
onChange={(settingValidateCertificates) => { onChange={(settingValidateCertificates) =>
trackEvent('workspace', 'update', { updateWorkspace.mutate({ settingValidateCertificates })
validate_certificates: JSON.stringify(settingValidateCertificates), }
});
updateWorkspace.mutate({ settingValidateCertificates });
}}
/> />
<Checkbox <Checkbox
checked={workspace.settingFollowRedirects} checked={workspace.settingFollowRedirects}
title="Follow Redirects" title="Follow Redirects"
onChange={(settingFollowRedirects) => { onChange={(settingFollowRedirects) => updateWorkspace.mutate({ settingFollowRedirects })}
trackEvent('workspace', 'update', {
follow_redirects: JSON.stringify(settingFollowRedirects),
});
updateWorkspace.mutate({ settingFollowRedirects });
}}
/> />
</VStack> </VStack>

View File

@@ -133,27 +133,31 @@ export function Sidebar({ className }: Props) {
) { ) {
selectedRequest = node.item; selectedRequest = node.item;
} }
const childItems = [...requests, ...folders].filter((f) => const childItems = [...requests, ...folders].filter((f) =>
node.item.model === 'workspace' ? f.folderId == null : f.folderId === node.item.id, node.item.model === 'workspace' ? f.folderId == null : f.folderId === node.item.id,
); );
childItems.sort((a, b) => a.sortPriority - b.sortPriority); // Recurse to children
const isCollapsed = collapsed.value?.[node.item.id];
const depth = node.depth + 1; const depth = node.depth + 1;
childItems.sort((a, b) => a.sortPriority - b.sortPriority);
for (const item of childItems) { for (const item of childItems) {
treeParentMap[item.id] = node; treeParentMap[item.id] = node;
// Add to children
node.children.push(next({ item, children: [], depth })); node.children.push(next({ item, children: [], depth }));
if (item.model !== 'folder') { // Add to selectable requests
if (item.model !== 'folder' && !isCollapsed) {
selectableRequests.push({ id: item.id, index: selectableRequestIndex++, tree: node }); selectableRequests.push({ id: item.id, index: selectableRequestIndex++, tree: node });
} }
} }
return node; return node;
}; };
const tree = next({ item: activeWorkspace, children: [], depth: 0 }); const tree = next({ item: activeWorkspace, children: [], depth: 0 });
return { tree, treeParentMap, selectableRequests, selectedRequest }; return { tree, treeParentMap, selectableRequests, selectedRequest };
}, [activeWorkspace, selectedId, requests, folders]); }, [activeWorkspace, selectedId, requests, folders, collapsed.value]);
const deleteSelectedRequest = useDeleteRequest(selectedRequest); const deleteSelectedRequest = useDeleteRequest(selectedRequest);
@@ -455,6 +459,7 @@ export function Sidebar({ className }: Props) {
/> />
<SidebarItems <SidebarItems
treeParentMap={treeParentMap} treeParentMap={treeParentMap}
activeId={activeRequest?.id ?? null}
selectedId={selectedId} selectedId={selectedId}
selectedTree={selectedTree} selectedTree={selectedTree}
isCollapsed={isCollapsed} isCollapsed={isCollapsed}
@@ -476,6 +481,7 @@ interface SidebarItemsProps {
tree: TreeNode; tree: TreeNode;
focused: boolean; focused: boolean;
draggingId: string | null; draggingId: string | null;
activeId: string | null;
selectedId: string | null; selectedId: string | null;
selectedTree: TreeNode | null; selectedTree: TreeNode | null;
treeParentMap: Record<string, TreeNode>; treeParentMap: Record<string, TreeNode>;
@@ -491,6 +497,7 @@ interface SidebarItemsProps {
function SidebarItems({ function SidebarItems({
tree, tree,
focused, focused,
activeId,
selectedId, selectedId,
selectedTree, selectedTree,
draggingId, draggingId,
@@ -517,6 +524,7 @@ function SidebarItems({
> >
{tree.children.map((child, i) => { {tree.children.map((child, i) => {
const selected = selectedId === child.item.id; const selected = selectedId === child.item.id;
const active = activeId === child.item.id;
return ( return (
<Fragment key={child.item.id}> <Fragment key={child.item.id}>
{hoveredIndex === i && hoveredTree?.item.id === tree.item.id && <DropMarker />} {hoveredIndex === i && hoveredTree?.item.id === tree.item.id && <DropMarker />}
@@ -535,7 +543,7 @@ function SidebarItems({
(child.item.model === 'http_request' || child.item.model === 'grpc_request') && ( (child.item.model === 'http_request' || child.item.model === 'grpc_request') && (
<HttpMethodTag <HttpMethodTag
request={child.item} request={child.item}
className={classNames(!selected && 'text-fg-subtler')} className={classNames(!(active || selected) && 'text-fg-subtler')}
/> />
) )
} }
@@ -558,6 +566,7 @@ function SidebarItems({
hoveredTree={hoveredTree} hoveredTree={hoveredTree}
hoveredIndex={hoveredIndex} hoveredIndex={hoveredIndex}
focused={focused} focused={focused}
activeId={activeId}
selectedId={selectedId} selectedId={selectedId}
selectedTree={selectedTree} selectedTree={selectedTree}
onSelect={onSelect} onSelect={onSelect}

View File

@@ -18,6 +18,7 @@ import { useOsInfo } from '../hooks/useOsInfo';
import { useShouldFloatSidebar } from '../hooks/useShouldFloatSidebar'; import { useShouldFloatSidebar } from '../hooks/useShouldFloatSidebar';
import { useSidebarHidden } from '../hooks/useSidebarHidden'; import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { useSidebarWidth } from '../hooks/useSidebarWidth'; import { useSidebarWidth } from '../hooks/useSidebarWidth';
import { useSyncWorkspaceRequestTitle } from '../hooks/useSyncWorkspaceRequestTitle';
import { useWorkspaces } from '../hooks/useWorkspaces'; import { useWorkspaces } from '../hooks/useWorkspaces';
import { Banner } from './core/Banner'; import { Banner } from './core/Banner';
import { Button } from './core/Button'; import { Button } from './core/Button';
@@ -40,6 +41,7 @@ const body = { gridArea: 'body' };
const drag = { gridArea: 'drag' }; const drag = { gridArea: 'drag' };
export default function Workspace() { export default function Workspace() {
useSyncWorkspaceRequestTitle();
const workspaces = useWorkspaces(); const workspaces = useWorkspaces();
const activeWorkspace = useActiveWorkspace(); const activeWorkspace = useActiveWorkspace();
const activeWorkspaceId = useActiveWorkspaceId(); const activeWorkspaceId = useActiveWorkspaceId();

View File

@@ -373,7 +373,6 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
bottom: upsideDown ? docRect.height - top : undefined, bottom: upsideDown ? docRect.height - top : 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,
width: containerWidth ?? 'auto',
}; };
const size = { top: '-0.2rem', width: '0.4rem', height: '0.4rem' }; const size = { top: '-0.2rem', width: '0.4rem', height: '0.4rem' };
const triangleStyles = onRight const triangleStyles = onRight
@@ -448,7 +447,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
<HStack <HStack
space={2} space={2}
alignItems="center" alignItems="center"
className="pb-0.5 px-1.5 mb-2 text-sm border border-background-highlight-secondary mx-2 rounded font-mono h-2xs" className="pb-0.5 px-1.5 mb-2 text-sm border border-background-highlight-secondary mx-2 rounded font-mono h-xs"
> >
<Icon icon="search" size="xs" className="text-fg-subtle" /> <Icon icon="search" size="xs" className="text-fg-subtle" />
<div className="text-fg">{filter}</div> <div className="text-fg">{filter}</div>

View File

@@ -17,6 +17,7 @@ import {
} from 'react'; } from 'react';
import { useActiveEnvironment } from '../../../hooks/useActiveEnvironment'; import { useActiveEnvironment } from '../../../hooks/useActiveEnvironment';
import { useActiveWorkspace } from '../../../hooks/useActiveWorkspace'; import { useActiveWorkspace } from '../../../hooks/useActiveWorkspace';
import { useSettings } from '../../../hooks/useSettings';
import { IconButton } from '../IconButton'; import { IconButton } from '../IconButton';
import { HStack } from '../Stacks'; import { HStack } from '../Stacks';
import './Editor.css'; import './Editor.css';
@@ -85,11 +86,16 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
}: EditorProps, }: EditorProps,
ref, ref,
) { ) {
const s = useSettings();
const e = useActiveEnvironment(); const e = useActiveEnvironment();
const w = useActiveWorkspace(); const w = useActiveWorkspace();
const environment = autocompleteVariables ? e : null; const environment = autocompleteVariables ? e : null;
const workspace = autocompleteVariables ? w : null; const workspace = autocompleteVariables ? w : null;
if (s && wrapLines === undefined) {
wrapLines = s.editorSoftWrap;
}
const cm = useRef<{ view: EditorView; languageCompartment: Compartment } | null>(null); const cm = useRef<{ view: EditorView; languageCompartment: Compartment } | null>(null);
useImperativeHandle(ref, () => cm.current?.view); useImperativeHandle(ref, () => cm.current?.view);

View File

@@ -42,6 +42,7 @@ const icons = {
magicWand: lucide.Wand2Icon, magicWand: lucide.Wand2Icon,
minus: lucide.MinusIcon, minus: lucide.MinusIcon,
moreVertical: lucide.MoreVerticalIcon, moreVertical: lucide.MoreVerticalIcon,
moon: lucide.MoonIcon,
paste: lucide.ClipboardPasteIcon, paste: lucide.ClipboardPasteIcon,
pencil: lucide.PencilIcon, pencil: lucide.PencilIcon,
pin: lucide.PinIcon, pin: lucide.PinIcon,
@@ -55,6 +56,7 @@ const icons = {
settings2: lucide.Settings2Icon, settings2: lucide.Settings2Icon,
settings: lucide.SettingsIcon, settings: lucide.SettingsIcon,
sparkles: lucide.SparklesIcon, sparkles: lucide.SparklesIcon,
sun: lucide.SunIcon,
trash: lucide.TrashIcon, trash: lucide.TrashIcon,
update: lucide.RefreshCcwIcon, update: lucide.RefreshCcwIcon,
upload: lucide.UploadIcon, upload: lucide.UploadIcon,

View File

@@ -1,5 +1,7 @@
import classNames from 'classnames'; import classNames from 'classnames';
import type { CSSProperties } from 'react'; import type { CSSProperties, ReactNode } from 'react';
import { useState } from 'react';
import { HStack } from './Stacks';
interface Props<T extends string> { interface Props<T extends string> {
name: string; name: string;
@@ -8,6 +10,7 @@ interface Props<T extends string> {
labelClassName?: string; labelClassName?: string;
hideLabel?: boolean; hideLabel?: boolean;
value: T; value: T;
leftSlot?: ReactNode;
options: SelectOption<T>[]; options: SelectOption<T>[];
onChange: (value: T) => void; onChange: (value: T) => void;
size?: 'xs' | 'sm' | 'md' | 'lg'; size?: 'xs' | 'sm' | 'md' | 'lg';
@@ -27,10 +30,12 @@ export function Select<T extends string>({
label, label,
value, value,
options, options,
leftSlot,
onChange, onChange,
className, className,
size = 'md', size = 'md',
}: Props<T>) { }: Props<T>) {
const [focused, setFocused] = useState<boolean>(false);
const id = `input-${name}`; const id = `input-${name}`;
return ( return (
<div <div
@@ -49,25 +54,36 @@ export function Select<T extends string>({
> >
{label} {label}
</label> </label>
<select <HStack
value={value} space={2}
style={selectBackgroundStyles} alignItems="center"
onChange={(e) => onChange(e.target.value as T)}
className={classNames( className={classNames(
'font-mono text-sm border w-full outline-none bg-transparent pl-2 pr-7', 'w-full rounded-md text-fg text-sm font-mono',
'bg-background-highlight-secondary border-background-highlight focus:border-border-focus', 'pl-2',
'bg-background-highlight-secondary border',
focused ? 'border-border-focus' : 'border-background-highlight',
size === 'xs' && 'h-xs', size === 'xs' && 'h-xs',
size === 'sm' && 'h-sm', size === 'sm' && 'h-sm',
size === 'md' && 'h-md', size === 'md' && 'h-md',
size === 'lg' && 'h-lg', size === 'lg' && 'h-lg',
)} )}
> >
{options.map(({ label, value }) => ( {leftSlot && <div>{leftSlot}</div>}
<option key={label} value={value}> <select
{label} value={value}
</option> style={selectBackgroundStyles}
))} onChange={(e) => onChange(e.target.value as T)}
</select> onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
className={classNames('pr-7 w-full outline-none bg-transparent')}
>
{options.map(({ label, value }) => (
<option key={label} value={value}>
{label}
</option>
))}
</select>
</HStack>
</div> </div>
); );
} }

View File

@@ -12,7 +12,6 @@ export function useSettings() {
queryKey: settingsQueryKey(), queryKey: settingsQueryKey(),
queryFn: async () => { queryFn: async () => {
const settings = (await invoke('cmd_get_settings')) as Settings; const settings = (await invoke('cmd_get_settings')) as Settings;
console.log('SETTINGS', settings);
return [settings]; return [settings];
}, },
}).data?.[0] ?? undefined }).data?.[0] ?? undefined

View File

@@ -7,7 +7,7 @@ import { useActiveWorkspace } from './useActiveWorkspace';
import { useOsInfo } from './useOsInfo'; import { useOsInfo } from './useOsInfo';
import { emit } from '@tauri-apps/api/event'; import { emit } from '@tauri-apps/api/event';
export function useSyncWindowTitle() { export function useSyncWorkspaceRequestTitle() {
const activeRequest = useActiveRequest(); const activeRequest = useActiveRequest();
const activeWorkspace = useActiveWorkspace(); const activeWorkspace = useActiveWorkspace();
const activeEnvironment = useActiveEnvironment(); const activeEnvironment = useActiveEnvironment();

View File

@@ -1,17 +1,16 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import type { Settings } from '../lib/models'; import type { Settings } from '../lib/models';
import { settingsQueryKey } from './useSettings'; import { useSettings } from './useSettings';
export function useUpdateSettings() { export function useUpdateSettings() {
const queryClient = useQueryClient(); const settings = useSettings();
return useMutation<void, unknown, Settings>({ return useMutation<void, unknown, Partial<Settings>>({
mutationFn: async (settings) => { mutationFn: async (patch) => {
await invoke('cmd_update_settings', { settings }); if (settings == null) return;
}, const newSettings: Settings = { ...settings, ...patch };
onMutate: async (settings) => { await invoke('cmd_update_settings', { settings: newSettings });
queryClient.setQueryData<Settings[]>(settingsQueryKey(), [settings]);
}, },
}); });
} }

View File

@@ -9,7 +9,6 @@ export function useZoom() {
const zoomIn = useCallback(() => { const zoomIn = useCallback(() => {
if (!settings) return; if (!settings) return;
updateSettings.mutate({ updateSettings.mutate({
...settings,
interfaceScale: Math.min(1.8, settings.interfaceScale * 1.1), interfaceScale: Math.min(1.8, settings.interfaceScale * 1.1),
}); });
}, [settings, updateSettings]); }, [settings, updateSettings]);
@@ -17,13 +16,11 @@ export function useZoom() {
const zoomOut = useCallback(() => { const zoomOut = useCallback(() => {
if (!settings) return; if (!settings) return;
updateSettings.mutate({ updateSettings.mutate({
...settings,
interfaceScale: Math.max(0.4, settings.interfaceScale * 0.9), interfaceScale: Math.max(0.4, settings.interfaceScale * 0.9),
}); });
}, [settings, updateSettings]); }, [settings, updateSettings]);
const zoomReset = useCallback(() => { const zoomReset = useCallback(() => {
if (!settings) return;
updateSettings.mutate({ ...settings, interfaceScale: 1 }); updateSettings.mutate({ ...settings, interfaceScale: 1 });
}, [settings, updateSettings]); }, [settings, updateSettings]);

View File

@@ -40,7 +40,7 @@ export interface Settings extends BaseModel {
interfaceFontSize: number; interfaceFontSize: number;
interfaceScale: number; interfaceScale: number;
editorFontSize: number; editorFontSize: number;
editorSoftWrap: number; editorSoftWrap: boolean;
} }
export interface Workspace extends BaseModel { export interface Workspace extends BaseModel {

View File

@@ -1,10 +1,9 @@
const plugin = require('tailwindcss/plugin'); const plugin = require('tailwindcss/plugin');
const height = { const height = {
'2xs': '1.6rem',
xs: '1.8rem', xs: '1.8rem',
sm: '2.2rem', sm: '2.0rem',
md: '2.7rem', md: '2.5rem',
}; };
/** @type {import("tailwindcss").Config} */ /** @type {import("tailwindcss").Config} */