mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-11 20:00:29 +01:00
Tweak settings for release
This commit is contained in:
@@ -23,7 +23,7 @@ use sqlx::{Pool, Sqlite, SqlitePool};
|
||||
use sqlx::migrate::Migrator;
|
||||
use sqlx::sqlite::SqliteConnectOptions;
|
||||
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::path::BaseDirectory;
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -74,6 +74,9 @@ mod tauri_plugin_mac_window;
|
||||
#[cfg(target_os = "windows")]
|
||||
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> {
|
||||
let pool = &*db.lock().await;
|
||||
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");
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -1840,7 +1808,7 @@ fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
|
||||
.resizable(true)
|
||||
.fullscreen(false)
|
||||
.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(
|
||||
// Randomly offset so windows don't stack exactly
|
||||
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 win2 = win.clone();
|
||||
let webview_window = win.clone();
|
||||
win.on_menu_event(move |w, event| {
|
||||
if !w.is_focused().unwrap() {
|
||||
return;
|
||||
@@ -1874,23 +1842,26 @@ fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
|
||||
|
||||
match event.id().0.as_str() {
|
||||
"quit" => exit(0),
|
||||
"close" => _ = w.close(),
|
||||
"close" => w.close().unwrap(),
|
||||
"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
|
||||
_ = webview_window
|
||||
.app_handle()
|
||||
.shell()
|
||||
.open("https://yaak.canny.io", None)
|
||||
}
|
||||
"toggle_devtools" => {
|
||||
if win2.is_devtools_open() {
|
||||
win2.close_devtools();
|
||||
|
||||
// Commands for development
|
||||
"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 {
|
||||
win2.open_devtools();
|
||||
webview_window.open_devtools();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -126,12 +126,14 @@ pub fn app_menu(app_handle: &AppHandle) -> tauri::Result<Menu<Wry>> {
|
||||
"Develop",
|
||||
true,
|
||||
&[
|
||||
&MenuItemBuilder::with_id("refresh".to_string(), "Refresh")
|
||||
&MenuItemBuilder::with_id("dev.refresh".to_string(), "Refresh")
|
||||
.accelerator("CmdOrCtrl+Shift+r")
|
||||
.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")
|
||||
.build(app_handle)?,
|
||||
&MenuItemBuilder::with_id("dev.reset_size".to_string(), "Reset Size")
|
||||
.build(app_handle)?,
|
||||
],
|
||||
)?,
|
||||
],
|
||||
|
||||
@@ -3,7 +3,7 @@ import { routePaths, useAppRoutes } from '../hooks/useAppRoutes';
|
||||
import { DefaultLayout } from './DefaultLayout';
|
||||
import { RedirectToLatestWorkspace } from './RedirectToLatestWorkspace';
|
||||
import RouteError from './RouteError';
|
||||
import { SettingsDialog } from './Settings/SettingsDialog';
|
||||
import { Settings } from './Settings/Settings';
|
||||
import Workspace from './Workspace';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
@@ -41,7 +41,7 @@ const router = createBrowserRouter([
|
||||
path: routePaths.workspaceSettings({
|
||||
workspaceId: ':workspaceId',
|
||||
}),
|
||||
element: <SettingsDialog fullscreen />,
|
||||
element: <Settings />,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -20,7 +20,6 @@ import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
import { settingsQueryKey, useSettings } from '../hooks/useSettings';
|
||||
import { useSyncThemeToDocument } from '../hooks/useSyncThemeToDocument';
|
||||
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
|
||||
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
||||
import { useZoom } from '../hooks/useZoom';
|
||||
import type { Model } from '../lib/models';
|
||||
@@ -34,7 +33,6 @@ export function GlobalHooks() {
|
||||
|
||||
// Other useful things
|
||||
useSyncThemeToDocument();
|
||||
useSyncWindowTitle();
|
||||
useGlobalCommands();
|
||||
useCommandPalette();
|
||||
useNotificationToast();
|
||||
|
||||
47
src-web/components/Settings/Settings.tsx
Normal file
47
src-web/components/Settings/Settings.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -5,19 +5,23 @@ import { useResolvedTheme } from '../../hooks/useResolvedTheme';
|
||||
import { useSettings } from '../../hooks/useSettings';
|
||||
import { useThemes } from '../../hooks/useThemes';
|
||||
import { useUpdateSettings } from '../../hooks/useUpdateSettings';
|
||||
import { trackEvent } from '../../lib/analytics';
|
||||
import { clamp } from '../../lib/clamp';
|
||||
import { isThemeDark } from '../../lib/theme/window';
|
||||
import type { ButtonProps } from '../core/Button';
|
||||
import { Checkbox } from '../core/Checkbox';
|
||||
import { Editor } from '../core/Editor';
|
||||
import type { IconProps } from '../core/Icon';
|
||||
import { Icon } from '../core/Icon';
|
||||
import { IconButton } from '../core/IconButton';
|
||||
import { PlainInput } from '../core/PlainInput';
|
||||
import type { SelectOption } from '../core/Select';
|
||||
import { Select } from '../core/Select';
|
||||
import { Separator } from '../core/Separator';
|
||||
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'][] = [
|
||||
'primary',
|
||||
'info',
|
||||
@@ -75,39 +79,28 @@ export function SettingsAppearance() {
|
||||
|
||||
return (
|
||||
<VStack space={2} className="mb-4">
|
||||
<PlainInput
|
||||
<Select
|
||||
size="sm"
|
||||
name="interfaceFontSize"
|
||||
label="Font Size"
|
||||
placeholder="16"
|
||||
step={0.5}
|
||||
type="number"
|
||||
labelPosition="left"
|
||||
defaultValue={`${settings.interfaceFontSize}`}
|
||||
validate={(value) => parseInt(value) >= 8 && parseInt(value) <= 30}
|
||||
onChange={(v) =>
|
||||
updateSettings.mutate({
|
||||
...settings,
|
||||
interfaceFontSize: clamp(parseInt(v) || 16, 8, 30),
|
||||
})
|
||||
}
|
||||
value={`${settings.interfaceFontSize}`}
|
||||
options={fontSizes}
|
||||
onChange={(v) => updateSettings.mutate({ interfaceFontSize: parseInt(v) })}
|
||||
/>
|
||||
<PlainInput
|
||||
<Select
|
||||
size="sm"
|
||||
name="editorFontSize"
|
||||
label="Editor Font Size"
|
||||
placeholder="14"
|
||||
step={0.5}
|
||||
type="number"
|
||||
labelPosition="left"
|
||||
defaultValue={`${settings.editorFontSize}`}
|
||||
validate={(value) => parseInt(value) >= 8 && parseInt(value) <= 30}
|
||||
onChange={(v) =>
|
||||
updateSettings.mutate({
|
||||
...settings,
|
||||
editorFontSize: clamp(parseInt(v) || 14, 8, 30),
|
||||
})
|
||||
}
|
||||
value={`${settings.editorFontSize}`}
|
||||
options={fontSizes}
|
||||
onChange={(v) => updateSettings.mutate({ editorFontSize: clamp(parseInt(v) || 14, 8, 30) })}
|
||||
/>
|
||||
<Checkbox
|
||||
checked={settings.editorSoftWrap}
|
||||
title="Wrap Editor Lines"
|
||||
onChange={(editorSoftWrap) => updateSettings.mutate({ editorSoftWrap })}
|
||||
/>
|
||||
|
||||
<Separator className="my-4" />
|
||||
@@ -118,9 +111,8 @@ export function SettingsAppearance() {
|
||||
labelPosition="left"
|
||||
size="sm"
|
||||
value={settings.appearance}
|
||||
onChange={async (appearance) => {
|
||||
await updateSettings.mutateAsync({ ...settings, appearance });
|
||||
trackEvent('setting', 'update', { appearance });
|
||||
onChange={(appearance) => {
|
||||
updateSettings.mutateAsync({ appearance });
|
||||
}}
|
||||
options={[
|
||||
{ label: 'Automatic', value: 'system' },
|
||||
@@ -128,42 +120,41 @@ export function SettingsAppearance() {
|
||||
{ label: 'Dark', value: 'dark' },
|
||||
]}
|
||||
/>
|
||||
{(settings.appearance === 'system' || settings.appearance === 'light') && (
|
||||
<Select
|
||||
name="lightTheme"
|
||||
label={settings.appearance === 'system' ? 'Light Theme' : 'Theme'}
|
||||
labelPosition="left"
|
||||
size="sm"
|
||||
value={activeTheme.light.id}
|
||||
options={lightThemes}
|
||||
onChange={async (themeLight) => {
|
||||
await updateSettings.mutateAsync({ ...settings, themeLight });
|
||||
trackEvent('setting', 'update', { themeLight });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{(settings.appearance === 'system' || settings.appearance === 'dark') && (
|
||||
<Select
|
||||
name="darkTheme"
|
||||
label={settings.appearance === 'system' ? 'Dark Theme' : 'Theme'}
|
||||
labelPosition="left"
|
||||
size="sm"
|
||||
value={activeTheme.dark.id}
|
||||
options={darkThemes}
|
||||
onChange={async (themeDark) => {
|
||||
await updateSettings.mutateAsync({ ...settings, themeDark });
|
||||
trackEvent('setting', 'update', { themeDark });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<HStack space={2}>
|
||||
{(settings.appearance === 'system' || settings.appearance === 'light') && (
|
||||
<Select
|
||||
hideLabel
|
||||
leftSlot={<Icon icon="sun" />}
|
||||
name="lightTheme"
|
||||
label="Light Theme"
|
||||
size="sm"
|
||||
value={activeTheme.light.id}
|
||||
options={lightThemes}
|
||||
onChange={(themeLight) => updateSettings.mutateAsync({ ...settings, themeLight })}
|
||||
/>
|
||||
)}
|
||||
{(settings.appearance === 'system' || settings.appearance === 'dark') && (
|
||||
<Select
|
||||
hideLabel
|
||||
name="darkTheme"
|
||||
label="Dark Theme"
|
||||
leftSlot={<Icon icon="moon" />}
|
||||
size="sm"
|
||||
value={activeTheme.dark.id}
|
||||
options={darkThemes}
|
||||
onChange={(themeDark) => updateSettings.mutateAsync({ ...settings, themeDark })}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
<VStack
|
||||
space={3}
|
||||
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">
|
||||
Theme Preview <span className="text-fg-subtle">({appearance})</span>
|
||||
</div>
|
||||
<HStack className="text-fg font-bold" alignItems="center" space={2}>
|
||||
Theme Preview{' '}
|
||||
<Icon icon={appearance === 'dark' ? 'moon' : 'sun'} className="text-fg-subtle" />
|
||||
</HStack>
|
||||
<HStack space={1.5} alignItems="center" className="w-full">
|
||||
{buttonColors.map((c, i) => (
|
||||
<IconButton
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -5,7 +5,6 @@ import { useCheckForUpdates } from '../../hooks/useCheckForUpdates';
|
||||
import { useSettings } from '../../hooks/useSettings';
|
||||
import { useUpdateSettings } from '../../hooks/useUpdateSettings';
|
||||
import { useUpdateWorkspace } from '../../hooks/useUpdateWorkspace';
|
||||
import { trackEvent } from '../../lib/analytics';
|
||||
import { Checkbox } from '../core/Checkbox';
|
||||
import { Heading } from '../core/Heading';
|
||||
import { IconButton } from '../core/IconButton';
|
||||
@@ -36,10 +35,7 @@ export function SettingsGeneral() {
|
||||
labelPosition="left"
|
||||
size="sm"
|
||||
value={settings.updateChannel}
|
||||
onChange={(updateChannel) => {
|
||||
trackEvent('setting', 'update', { update_channel: updateChannel });
|
||||
updateSettings.mutate({ ...settings, updateChannel });
|
||||
}}
|
||||
onChange={(updateChannel) => updateSettings.mutate({ updateChannel })}
|
||||
options={[
|
||||
{ label: 'Release', value: 'stable' },
|
||||
{ label: 'Early Bird (Beta)', value: 'beta' },
|
||||
@@ -77,23 +73,15 @@ export function SettingsGeneral() {
|
||||
<Checkbox
|
||||
checked={workspace.settingValidateCertificates}
|
||||
title="Validate TLS Certificates"
|
||||
onChange={(settingValidateCertificates) => {
|
||||
trackEvent('workspace', 'update', {
|
||||
validate_certificates: JSON.stringify(settingValidateCertificates),
|
||||
});
|
||||
updateWorkspace.mutate({ settingValidateCertificates });
|
||||
}}
|
||||
onChange={(settingValidateCertificates) =>
|
||||
updateWorkspace.mutate({ settingValidateCertificates })
|
||||
}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
checked={workspace.settingFollowRedirects}
|
||||
title="Follow Redirects"
|
||||
onChange={(settingFollowRedirects) => {
|
||||
trackEvent('workspace', 'update', {
|
||||
follow_redirects: JSON.stringify(settingFollowRedirects),
|
||||
});
|
||||
updateWorkspace.mutate({ settingFollowRedirects });
|
||||
}}
|
||||
onChange={(settingFollowRedirects) => updateWorkspace.mutate({ settingFollowRedirects })}
|
||||
/>
|
||||
</VStack>
|
||||
|
||||
|
||||
@@ -133,27 +133,31 @@ export function Sidebar({ className }: Props) {
|
||||
) {
|
||||
selectedRequest = node.item;
|
||||
}
|
||||
|
||||
const childItems = [...requests, ...folders].filter((f) =>
|
||||
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;
|
||||
childItems.sort((a, b) => a.sortPriority - b.sortPriority);
|
||||
for (const item of childItems) {
|
||||
treeParentMap[item.id] = node;
|
||||
// Add to children
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
const tree = next({ item: activeWorkspace, children: [], depth: 0 });
|
||||
|
||||
return { tree, treeParentMap, selectableRequests, selectedRequest };
|
||||
}, [activeWorkspace, selectedId, requests, folders]);
|
||||
}, [activeWorkspace, selectedId, requests, folders, collapsed.value]);
|
||||
|
||||
const deleteSelectedRequest = useDeleteRequest(selectedRequest);
|
||||
|
||||
@@ -455,6 +459,7 @@ export function Sidebar({ className }: Props) {
|
||||
/>
|
||||
<SidebarItems
|
||||
treeParentMap={treeParentMap}
|
||||
activeId={activeRequest?.id ?? null}
|
||||
selectedId={selectedId}
|
||||
selectedTree={selectedTree}
|
||||
isCollapsed={isCollapsed}
|
||||
@@ -476,6 +481,7 @@ interface SidebarItemsProps {
|
||||
tree: TreeNode;
|
||||
focused: boolean;
|
||||
draggingId: string | null;
|
||||
activeId: string | null;
|
||||
selectedId: string | null;
|
||||
selectedTree: TreeNode | null;
|
||||
treeParentMap: Record<string, TreeNode>;
|
||||
@@ -491,6 +497,7 @@ interface SidebarItemsProps {
|
||||
function SidebarItems({
|
||||
tree,
|
||||
focused,
|
||||
activeId,
|
||||
selectedId,
|
||||
selectedTree,
|
||||
draggingId,
|
||||
@@ -517,6 +524,7 @@ function SidebarItems({
|
||||
>
|
||||
{tree.children.map((child, i) => {
|
||||
const selected = selectedId === child.item.id;
|
||||
const active = activeId === child.item.id;
|
||||
return (
|
||||
<Fragment key={child.item.id}>
|
||||
{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') && (
|
||||
<HttpMethodTag
|
||||
request={child.item}
|
||||
className={classNames(!selected && 'text-fg-subtler')}
|
||||
className={classNames(!(active || selected) && 'text-fg-subtler')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -558,6 +566,7 @@ function SidebarItems({
|
||||
hoveredTree={hoveredTree}
|
||||
hoveredIndex={hoveredIndex}
|
||||
focused={focused}
|
||||
activeId={activeId}
|
||||
selectedId={selectedId}
|
||||
selectedTree={selectedTree}
|
||||
onSelect={onSelect}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { useOsInfo } from '../hooks/useOsInfo';
|
||||
import { useShouldFloatSidebar } from '../hooks/useShouldFloatSidebar';
|
||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||
import { useSidebarWidth } from '../hooks/useSidebarWidth';
|
||||
import { useSyncWorkspaceRequestTitle } from '../hooks/useSyncWorkspaceRequestTitle';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
@@ -40,6 +41,7 @@ const body = { gridArea: 'body' };
|
||||
const drag = { gridArea: 'drag' };
|
||||
|
||||
export default function Workspace() {
|
||||
useSyncWorkspaceRequestTitle();
|
||||
const workspaces = useWorkspaces();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeWorkspaceId = useActiveWorkspaceId();
|
||||
|
||||
@@ -373,7 +373,6 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
||||
bottom: upsideDown ? docRect.height - top : undefined,
|
||||
right: onRight ? docRect.width - triggerShape?.right : undefined,
|
||||
left: !onRight ? triggerShape?.left : undefined,
|
||||
width: containerWidth ?? 'auto',
|
||||
};
|
||||
const size = { top: '-0.2rem', width: '0.4rem', height: '0.4rem' };
|
||||
const triangleStyles = onRight
|
||||
@@ -448,7 +447,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle'>, MenuPro
|
||||
<HStack
|
||||
space={2}
|
||||
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" />
|
||||
<div className="text-fg">{filter}</div>
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from 'react';
|
||||
import { useActiveEnvironment } from '../../../hooks/useActiveEnvironment';
|
||||
import { useActiveWorkspace } from '../../../hooks/useActiveWorkspace';
|
||||
import { useSettings } from '../../../hooks/useSettings';
|
||||
import { IconButton } from '../IconButton';
|
||||
import { HStack } from '../Stacks';
|
||||
import './Editor.css';
|
||||
@@ -85,11 +86,16 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
}: EditorProps,
|
||||
ref,
|
||||
) {
|
||||
const s = useSettings();
|
||||
const e = useActiveEnvironment();
|
||||
const w = useActiveWorkspace();
|
||||
const environment = autocompleteVariables ? e : null;
|
||||
const workspace = autocompleteVariables ? w : null;
|
||||
|
||||
if (s && wrapLines === undefined) {
|
||||
wrapLines = s.editorSoftWrap;
|
||||
}
|
||||
|
||||
const cm = useRef<{ view: EditorView; languageCompartment: Compartment } | null>(null);
|
||||
useImperativeHandle(ref, () => cm.current?.view);
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ const icons = {
|
||||
magicWand: lucide.Wand2Icon,
|
||||
minus: lucide.MinusIcon,
|
||||
moreVertical: lucide.MoreVerticalIcon,
|
||||
moon: lucide.MoonIcon,
|
||||
paste: lucide.ClipboardPasteIcon,
|
||||
pencil: lucide.PencilIcon,
|
||||
pin: lucide.PinIcon,
|
||||
@@ -55,6 +56,7 @@ const icons = {
|
||||
settings2: lucide.Settings2Icon,
|
||||
settings: lucide.SettingsIcon,
|
||||
sparkles: lucide.SparklesIcon,
|
||||
sun: lucide.SunIcon,
|
||||
trash: lucide.TrashIcon,
|
||||
update: lucide.RefreshCcwIcon,
|
||||
upload: lucide.UploadIcon,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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> {
|
||||
name: string;
|
||||
@@ -8,6 +10,7 @@ interface Props<T extends string> {
|
||||
labelClassName?: string;
|
||||
hideLabel?: boolean;
|
||||
value: T;
|
||||
leftSlot?: ReactNode;
|
||||
options: SelectOption<T>[];
|
||||
onChange: (value: T) => void;
|
||||
size?: 'xs' | 'sm' | 'md' | 'lg';
|
||||
@@ -27,10 +30,12 @@ export function Select<T extends string>({
|
||||
label,
|
||||
value,
|
||||
options,
|
||||
leftSlot,
|
||||
onChange,
|
||||
className,
|
||||
size = 'md',
|
||||
}: Props<T>) {
|
||||
const [focused, setFocused] = useState<boolean>(false);
|
||||
const id = `input-${name}`;
|
||||
return (
|
||||
<div
|
||||
@@ -49,25 +54,36 @@ export function Select<T extends string>({
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
<select
|
||||
value={value}
|
||||
style={selectBackgroundStyles}
|
||||
onChange={(e) => onChange(e.target.value as T)}
|
||||
<HStack
|
||||
space={2}
|
||||
alignItems="center"
|
||||
className={classNames(
|
||||
'font-mono text-sm border w-full outline-none bg-transparent pl-2 pr-7',
|
||||
'bg-background-highlight-secondary border-background-highlight focus:border-border-focus',
|
||||
'w-full rounded-md text-fg text-sm font-mono',
|
||||
'pl-2',
|
||||
'bg-background-highlight-secondary border',
|
||||
focused ? 'border-border-focus' : 'border-background-highlight',
|
||||
size === 'xs' && 'h-xs',
|
||||
size === 'sm' && 'h-sm',
|
||||
size === 'md' && 'h-md',
|
||||
size === 'lg' && 'h-lg',
|
||||
)}
|
||||
>
|
||||
{options.map(({ label, value }) => (
|
||||
<option key={label} value={value}>
|
||||
{label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{leftSlot && <div>{leftSlot}</div>}
|
||||
<select
|
||||
value={value}
|
||||
style={selectBackgroundStyles}
|
||||
onChange={(e) => onChange(e.target.value as T)}
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ export function useSettings() {
|
||||
queryKey: settingsQueryKey(),
|
||||
queryFn: async () => {
|
||||
const settings = (await invoke('cmd_get_settings')) as Settings;
|
||||
console.log('SETTINGS', settings);
|
||||
return [settings];
|
||||
},
|
||||
}).data?.[0] ?? undefined
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useActiveWorkspace } from './useActiveWorkspace';
|
||||
import { useOsInfo } from './useOsInfo';
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
|
||||
export function useSyncWindowTitle() {
|
||||
export function useSyncWorkspaceRequestTitle() {
|
||||
const activeRequest = useActiveRequest();
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const activeEnvironment = useActiveEnvironment();
|
||||
@@ -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 type { Settings } from '../lib/models';
|
||||
import { settingsQueryKey } from './useSettings';
|
||||
import { useSettings } from './useSettings';
|
||||
|
||||
export function useUpdateSettings() {
|
||||
const queryClient = useQueryClient();
|
||||
const settings = useSettings();
|
||||
|
||||
return useMutation<void, unknown, Settings>({
|
||||
mutationFn: async (settings) => {
|
||||
await invoke('cmd_update_settings', { settings });
|
||||
},
|
||||
onMutate: async (settings) => {
|
||||
queryClient.setQueryData<Settings[]>(settingsQueryKey(), [settings]);
|
||||
return useMutation<void, unknown, Partial<Settings>>({
|
||||
mutationFn: async (patch) => {
|
||||
if (settings == null) return;
|
||||
const newSettings: Settings = { ...settings, ...patch };
|
||||
await invoke('cmd_update_settings', { settings: newSettings });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ export function useZoom() {
|
||||
const zoomIn = useCallback(() => {
|
||||
if (!settings) return;
|
||||
updateSettings.mutate({
|
||||
...settings,
|
||||
interfaceScale: Math.min(1.8, settings.interfaceScale * 1.1),
|
||||
});
|
||||
}, [settings, updateSettings]);
|
||||
@@ -17,13 +16,11 @@ export function useZoom() {
|
||||
const zoomOut = useCallback(() => {
|
||||
if (!settings) return;
|
||||
updateSettings.mutate({
|
||||
...settings,
|
||||
interfaceScale: Math.max(0.4, settings.interfaceScale * 0.9),
|
||||
});
|
||||
}, [settings, updateSettings]);
|
||||
|
||||
const zoomReset = useCallback(() => {
|
||||
if (!settings) return;
|
||||
updateSettings.mutate({ ...settings, interfaceScale: 1 });
|
||||
}, [settings, updateSettings]);
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export interface Settings extends BaseModel {
|
||||
interfaceFontSize: number;
|
||||
interfaceScale: number;
|
||||
editorFontSize: number;
|
||||
editorSoftWrap: number;
|
||||
editorSoftWrap: boolean;
|
||||
}
|
||||
|
||||
export interface Workspace extends BaseModel {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
const plugin = require('tailwindcss/plugin');
|
||||
|
||||
const height = {
|
||||
'2xs': '1.6rem',
|
||||
xs: '1.8rem',
|
||||
sm: '2.2rem',
|
||||
md: '2.7rem',
|
||||
sm: '2.0rem',
|
||||
md: '2.5rem',
|
||||
};
|
||||
|
||||
/** @type {import("tailwindcss").Config} */
|
||||
|
||||
Reference in New Issue
Block a user