Initial settings implementation

This commit is contained in:
Gregory Schier
2024-01-11 21:13:17 -08:00
parent bd5ae12f2e
commit 1a64d7d9e6
18 changed files with 426 additions and 65 deletions

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO settings (id)\n VALUES ('default')\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 0
},
"nullable": []
},
"hash": "2c181a4dc13efc52fe6a5a68291c5678a9624020df4ea744e78396f6926d5c88"
}

View File

@@ -0,0 +1,56 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n id,\n model,\n created_at,\n updated_at,\n follow_redirects,\n validate_certificates,\n theme\n FROM settings\n WHERE id = 'default'\n ",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "model",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "created_at",
"ordinal": 2,
"type_info": "Datetime"
},
{
"name": "updated_at",
"ordinal": 3,
"type_info": "Datetime"
},
{
"name": "follow_redirects",
"ordinal": 4,
"type_info": "Bool"
},
{
"name": "validate_certificates",
"ordinal": 5,
"type_info": "Bool"
},
{
"name": "theme",
"ordinal": 6,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
false,
false,
false,
false,
false,
false
]
},
"hash": "a25253024aec650aff34b38684de49b94514f676863acf02e0411da1161af067"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n UPDATE settings SET (\n follow_redirects,\n validate_certificates,\n theme\n ) = (?, ?, ?) WHERE id = 'default';\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "daf3fc4a8c620af81be9e2ef25a4fd352d9498f9b6c7c567635edcbbe9ac5127"
}

View File

@@ -0,0 +1,11 @@
CREATE TABLE settings
(
id TEXT NOT NULL
PRIMARY KEY,
model TEXT DEFAULT 'settings' NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
follow_redirects BOOLEAN DEFAULT TRUE NOT NULL,
validate_certificates BOOLEAN DEFAULT TRUE NOT NULL,
theme TEXT DEFAULT 'system' NOT NULL
);

View File

@@ -521,6 +521,31 @@ async fn list_environments(
Ok(environments) Ok(environments)
} }
#[tauri::command]
async fn get_settings(
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<models::Settings, String> {
let pool = &*db_instance.lock().await;
models::get_or_create_settings(pool)
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn update_settings(
settings: models::Settings,
window: Window<Wry>,
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<models::Settings, String> {
let pool = &*db_instance.lock().await;
let updated_settings = models::update_settings(pool, settings)
.await
.expect("Failed to update settings");
emit_and_return(&window, "updated_model", updated_settings)
}
#[tauri::command] #[tauri::command]
async fn get_folder( async fn get_folder(
id: &str, id: &str,
@@ -731,6 +756,7 @@ fn main() {
get_environment, get_environment,
get_folder, get_folder,
get_request, get_request,
get_settings,
get_workspace, get_workspace,
import_data, import_data,
list_environments, list_environments,
@@ -747,6 +773,7 @@ fn main() {
update_environment, update_environment,
update_folder, update_folder,
update_request, update_request,
update_settings,
update_workspace, update_workspace,
]) ])
.build(tauri::generate_context!()) .build(tauri::generate_context!())

View File

@@ -3,11 +3,25 @@ use std::fs;
use rand::distributions::{Alphanumeric, DistString}; use rand::distributions::{Alphanumeric, DistString};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{Pool, Sqlite};
use sqlx::types::{Json, JsonValue};
use sqlx::types::chrono::NaiveDateTime; use sqlx::types::chrono::NaiveDateTime;
use sqlx::types::{Json, JsonValue};
use sqlx::{Pool, Sqlite};
use tauri::AppHandle; use tauri::AppHandle;
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default, rename_all = "camelCase")]
pub struct Settings {
pub id: String,
pub model: String,
pub created_at: NaiveDateTime,
pub updated_at: NaiveDateTime,
// Settings
pub validate_certificates: bool,
pub follow_redirects: bool,
pub theme: String,
}
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)] #[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
pub struct Workspace { pub struct Workspace {
@@ -192,7 +206,11 @@ pub async fn get_key_value(namespace: &str, key: &str, pool: &Pool<Sqlite>) -> O
.ok() .ok()
} }
pub async fn get_key_value_string(namespace: &str, key: &str, pool: &Pool<Sqlite>) -> Option<String> { pub async fn get_key_value_string(
namespace: &str,
key: &str,
pool: &Pool<Sqlite>,
) -> Option<String> {
let kv = get_key_value(namespace, key, pool).await?; let kv = get_key_value(namespace, key, pool).await?;
let result = serde_json::from_str(&kv.value); let result = serde_json::from_str(&kv.value);
match result { match result {
@@ -283,6 +301,64 @@ pub async fn delete_environment(id: &str, pool: &Pool<Sqlite>) -> Result<Environ
Ok(env) Ok(env)
} }
async fn get_settings(pool: &Pool<Sqlite>) -> Result<Settings, sqlx::Error> {
sqlx::query_as!(
Settings,
r#"
SELECT
id,
model,
created_at,
updated_at,
follow_redirects,
validate_certificates,
theme
FROM settings
WHERE id = 'default'
"#,
)
.fetch_one(pool)
.await
}
pub async fn get_or_create_settings(pool: &Pool<Sqlite>) -> Result<Settings, sqlx::Error> {
let existing = get_settings(pool).await;
if let Ok(s) = existing {
Ok(s)
} else {
sqlx::query!(
r#"
INSERT INTO settings (id)
VALUES ('default')
"#,
)
.execute(pool)
.await?;
get_settings(pool).await
}
}
pub async fn update_settings(
pool: &Pool<Sqlite>,
settings: Settings,
) -> Result<Settings, sqlx::Error> {
sqlx::query!(
r#"
UPDATE settings SET (
follow_redirects,
validate_certificates,
theme
) = (?, ?, ?) WHERE id = 'default';
"#,
settings.follow_redirects,
settings.validate_certificates,
settings.theme,
)
.execute(pool)
.await?;
get_settings(pool).await
}
pub async fn upsert_environment( pub async fn upsert_environment(
pool: &Pool<Sqlite>, pool: &Pool<Sqlite>,
environment: Environment, environment: Environment,

View File

@@ -34,12 +34,19 @@ pub async fn actually_send_request(
url_string = format!("http://{}", url_string); url_string = format!("http://{}", url_string);
} }
let settings = models::get_or_create_settings(pool)
.await
.expect("Failed to get settings");
let client = reqwest::Client::builder() let client = reqwest::Client::builder()
.redirect(Policy::none()) // TODO: Handle redirect manually .redirect(match settings.follow_redirects {
.danger_accept_invalid_certs(false) // TODO: Make this configurable true => Policy::limited(10), // TODO: Handle redirects natively
false => Policy::none(),
})
.danger_accept_invalid_certs(!settings.validate_certificates)
.connection_verbose(true) // TODO: Capture this log somehow .connection_verbose(true) // TODO: Capture this log somehow
.tls_info(true) // TODO: Capture this log somehow .tls_info(true)
// .use_rustls_tls() // TODO: Make this configurable // .use_rustls_tls() // TODO: Make this configurable (maybe)
.build() .build()
.expect("Failed to build client"); .expect("Failed to build client");
@@ -117,7 +124,9 @@ pub async fn actually_send_request(
let mut query_params = Vec::new(); let mut query_params = Vec::new();
for p in request.url_parameters.0 { for p in request.url_parameters.0 {
if !p.enabled || p.name.is_empty() { continue; } if !p.enabled || p.name.is_empty() {
continue;
}
query_params.push(( query_params.push((
render::render(&p.name, &workspace, environment_ref), render::render(&p.name, &workspace, environment_ref),
render::render(&p.value, &workspace, environment_ref), render::render(&p.value, &workspace, environment_ref),
@@ -131,18 +140,38 @@ pub async fn actually_send_request(
let request_body = request.body.0; let request_body = request.body.0;
if request_body.contains_key("text") { if request_body.contains_key("text") {
let raw_text = request_body.get("text").unwrap_or(empty_string).as_str().unwrap_or(""); let raw_text = request_body
.get("text")
.unwrap_or(empty_string)
.as_str()
.unwrap_or("");
let body = render::render(raw_text, &workspace, environment_ref); let body = render::render(raw_text, &workspace, environment_ref);
request_builder = request_builder.body(body); request_builder = request_builder.body(body);
} else if body_type == "application/x-www-form-urlencoded" && request_body.contains_key("form") { } else if body_type == "application/x-www-form-urlencoded"
&& request_body.contains_key("form")
{
let mut form_params = Vec::new(); let mut form_params = Vec::new();
let form = request_body.get("form"); let form = request_body.get("form");
if let Some(f) = form { if let Some(f) = form {
for p in f.as_array().unwrap_or(&Vec::new()) { for p in f.as_array().unwrap_or(&Vec::new()) {
let enabled = p.get("enabled").unwrap_or(empty_bool).as_bool().unwrap_or(false); let enabled = p
let name = p.get("name").unwrap_or(empty_string).as_str().unwrap_or_default(); .get("enabled")
if !enabled || name.is_empty() { continue; } .unwrap_or(empty_bool)
let value = p.get("value").unwrap_or(empty_string).as_str().unwrap_or_default(); .as_bool()
.unwrap_or(false);
let name = p
.get("name")
.unwrap_or(empty_string)
.as_str()
.unwrap_or_default();
if !enabled || name.is_empty() {
continue;
}
let value = p
.get("value")
.unwrap_or(empty_string)
.as_str()
.unwrap_or_default();
form_params.push(( form_params.push((
render::render(name, &workspace, environment_ref), render::render(name, &workspace, environment_ref),
render::render(value, &workspace, environment_ref), render::render(value, &workspace, environment_ref),
@@ -154,17 +183,41 @@ pub async fn actually_send_request(
let mut multipart_form = multipart::Form::new(); let mut multipart_form = multipart::Form::new();
if let Some(form_definition) = request_body.get("form") { if let Some(form_definition) = request_body.get("form") {
for p in form_definition.as_array().unwrap_or(&Vec::new()) { for p in form_definition.as_array().unwrap_or(&Vec::new()) {
let enabled = p.get("enabled").unwrap_or(empty_bool).as_bool().unwrap_or(false); let enabled = p
let name = p.get("name").unwrap_or(empty_string).as_str().unwrap_or_default(); .get("enabled")
if !enabled || name.is_empty() { continue; } .unwrap_or(empty_bool)
.as_bool()
.unwrap_or(false);
let name = p
.get("name")
.unwrap_or(empty_string)
.as_str()
.unwrap_or_default();
if !enabled || name.is_empty() {
continue;
}
let file = p.get("file").unwrap_or(empty_string).as_str().unwrap_or_default(); let file = p
let value = p.get("value").unwrap_or(empty_string).as_str().unwrap_or_default(); .get("file")
.unwrap_or(empty_string)
.as_str()
.unwrap_or_default();
let value = p
.get("value")
.unwrap_or(empty_string)
.as_str()
.unwrap_or_default();
multipart_form = multipart_form.part( multipart_form = multipart_form.part(
render::render(name, &workspace, environment_ref), render::render(name, &workspace, environment_ref),
match !file.is_empty() { match !file.is_empty() {
true => multipart::Part::bytes(fs::read(file).map_err(|e| e.to_string())?), true => {
false => multipart::Part::text(render::render(value, &workspace, environment_ref)), multipart::Part::bytes(fs::read(file).map_err(|e| e.to_string())?)
}
false => multipart::Part::text(render::render(
value,
&workspace,
environment_ref,
)),
}, },
); );
} }
@@ -243,6 +296,6 @@ pub async fn actually_send_request(
Err(e) => { Err(e) => {
println!("Yo: {}", e); println!("Yo: {}", e);
response_err(response, e.to_string(), app_handle, pool).await response_err(response, e.to_string(), app_handle, pool).await
}, }
} }
} }

View File

@@ -0,0 +1,33 @@
import { useSettings } from '../hooks/useSettings';
import { useTheme } from '../hooks/useTheme';
import { useUpdateSettings } from '../hooks/useUpdateSettings';
import { Checkbox } from './core/Checkbox';
import { VStack } from './core/Stacks';
export const SettingsDialog = () => {
const { appearance, toggleAppearance } = useTheme();
const settings = useSettings();
const updateSettings = useUpdateSettings();
if (settings == null) {
return null;
}
return (
<VStack space={2}>
<Checkbox
checked={settings.validateCertificates}
title="Validate TLS Certificates"
onChange={(validateCertificates) =>
updateSettings.mutateAsync({ ...settings, validateCertificates })
}
/>
<Checkbox
checked={settings.followRedirects}
title="Follow Redirects"
onChange={(followRedirects) => updateSettings.mutateAsync({ ...settings, followRedirects })}
/>
<Checkbox checked={appearance === 'dark'} title="Dark Mode" onChange={toggleAppearance} />
</VStack>
);
};

View File

@@ -13,6 +13,7 @@ import { IconButton } from './core/IconButton';
import { VStack } from './core/Stacks'; import { VStack } from './core/Stacks';
import { useDialog } from './DialogContext'; import { useDialog } from './DialogContext';
import { KeyboardShortcutsDialog } from './KeyboardShortcutsDialog'; import { KeyboardShortcutsDialog } from './KeyboardShortcutsDialog';
import { SettingsDialog } from './SettingsDialog';
export function SettingsDropdown() { export function SettingsDropdown() {
const importData = useImportData(); const importData = useImportData();
@@ -61,16 +62,11 @@ export function SettingsDropdown() {
leftSlot: <Icon icon="upload" />, leftSlot: <Icon icon="upload" />,
onSelect: () => exportData.mutate(), onSelect: () => exportData.mutate(),
}, },
{
key: 'appearance',
label: 'Toggle Theme',
onSelect: toggleAppearance,
leftSlot: <Icon icon={appearance === 'dark' ? 'sun' : 'moon'} />,
},
{ {
key: 'hotkeys', key: 'hotkeys',
label: 'Keyboard shortcuts', label: 'Keyboard shortcuts',
hotkeyAction: 'hotkeys.showHelp', hotkeyAction: 'hotkeys.showHelp',
leftSlot: <Icon icon="keyboard" />,
onSelect: () => { onSelect: () => {
dialog.show({ dialog.show({
id: 'hotkey-help', id: 'hotkey-help',
@@ -79,7 +75,20 @@ export function SettingsDropdown() {
render: () => <KeyboardShortcutsDialog />, render: () => <KeyboardShortcutsDialog />,
}); });
}, },
leftSlot: <Icon icon="keyboard" />, },
{
key: 'settings',
label: 'Settings',
hotkeyAction: 'settings.show',
leftSlot: <Icon icon="gear" />,
onSelect: () => {
dialog.show({
id: 'settings',
size: 'md',
title: 'Settings',
render: () => <SettingsDialog />,
});
},
}, },
{ type: 'separator', label: `Yaak v${appVersion.data}` }, { type: 'separator', label: `Yaak v${appVersion.data}` },
{ {

View File

@@ -1,6 +1,6 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { useCallback } from 'react';
import { Icon } from './Icon'; import { Icon } from './Icon';
import { HStack } from './Stacks';
interface Props { interface Props {
checked: boolean; checked: boolean;
@@ -8,33 +8,47 @@ interface Props {
onChange: (checked: boolean) => void; onChange: (checked: boolean) => void;
disabled?: boolean; disabled?: boolean;
className?: string; className?: string;
hideLabel?: boolean;
} }
export function Checkbox({ checked, onChange, className, disabled, title }: Props) { export function Checkbox({ checked, onChange, className, disabled, title, hideLabel }: Props) {
const handleClick = useCallback(() => {
onChange(!checked);
}, [onChange, checked]);
return ( return (
<button <HStack
role="checkbox" as="label"
aria-checked={checked ? 'true' : 'false'} space={2}
disabled={disabled} alignItems="center"
onClick={handleClick} className={classNames(className, disabled && 'opacity-disabled')}
title={title}
className={classNames(
className,
'flex-shrink-0 w-4 h-4 border border-gray-200 rounded',
'focus:border-focus',
'disabled:opacity-disabled',
checked && 'bg-gray-200/10',
// Remove focus style
'outline-none',
)}
> >
<div className="flex items-center justify-center"> <div className="relative flex">
<Icon size="sm" icon={checked ? 'check' : 'empty'} /> <input
aria-hidden
className="appearance-none w-4 h-4 flex-shrink-0 border border-gray-200 rounded focus:border-focus outline-none ring-0"
type="checkbox"
disabled={disabled}
onChange={() => onChange(!checked)}
/>
<div className="absolute inset-0 flex items-center justify-center">
<Icon size="sm" icon={checked ? 'check' : 'empty'} />
</div>
</div> </div>
</button> {/*<button*/}
{/* role="checkbox"*/}
{/* aria-checked={checked ? 'true' : 'false'}*/}
{/* disabled={disabled}*/}
{/* onClick={handleClick}*/}
{/* title={title}*/}
{/* className={classNames(*/}
{/* className,*/}
{/* 'flex-shrink-0 w-4 h-4 border border-gray-200 rounded',*/}
{/* 'focus:border-focus',*/}
{/* 'disabled:opacity-disabled',*/}
{/* checked && 'bg-gray-200/10',*/}
{/* // Remove focus style*/}
{/* 'outline-none',*/}
{/* )}*/}
{/*>*/}
{/*</button>*/}
{!hideLabel && title}
</HStack>
); );
} }

View File

@@ -479,11 +479,6 @@ interface MenuItemHotKeyProps {
} }
function MenuItemHotKey({ action, onSelect, item }: MenuItemHotKeyProps) { function MenuItemHotKey({ action, onSelect, item }: MenuItemHotKeyProps) {
if (action) { useHotKey(action ?? null, () => onSelect(item));
console.log('MENU ITEM HOTKEY', action, item);
}
useHotKey(action ?? null, () => {
onSelect(item);
});
return null; return null;
} }

View File

@@ -290,13 +290,29 @@ function getExtensions({
// Handle onChange // Handle onChange
EditorView.updateListener.of((update) => { EditorView.updateListener.of((update) => {
if (onChange && update.docChanged) { // Only fire onChange if the document changed and the update was from user input. This prevents firing onChange when the document is updated when
// changing pages (one request to another in header editor)
if (onChange && update.docChanged && isViewUpdateFromUserInput(update)) {
onChange.current?.(update.state.doc.toString()); onChange.current?.(update.state.doc.toString());
} }
}), }),
]; ];
} }
function isViewUpdateFromUserInput(viewUpdate: ViewUpdate) {
// Make sure document has changed, ensuring user events like selections don't count.
if (viewUpdate.docChanged) {
// Check transactions for any that are direct user input, not changes from Y.js or another extension.
for (const transaction of viewUpdate.transactions) {
// Not using Transaction.isUserEvent because that only checks for a specific User event type ( "input", "delete", etc.). Checking the annotation directly allows for any type of user event.
const userEventType = transaction.annotation(Transaction.userEvent);
if (userEventType) return userEventType;
}
}
return false;
}
const syncGutterBg = ({ const syncGutterBg = ({
parent, parent,
className = '', className = '',

View File

@@ -335,7 +335,8 @@ const FormRow = memo(function FormRow({
<span className="w-3" /> <span className="w-3" />
)} )}
<Checkbox <Checkbox
title={pairContainer.pair.enabled ? 'disable entry' : 'Enable item'} hideLabel
title={pairContainer.pair.enabled ? 'Disable item' : 'Enable item'}
disabled={isLast} disabled={isLast}
checked={isLast ? false : !!pairContainer.pair.enabled} checked={isLast ? false : !!pairContainer.pair.enabled}
className={classNames('mr-2', isLast && '!opacity-disabled')} className={classNames('mr-2', isLast && '!opacity-disabled')}

View File

@@ -54,7 +54,7 @@ export const VStack = forwardRef(function VStack(
}); });
type BaseStackProps = HTMLAttributes<HTMLElement> & { type BaseStackProps = HTMLAttributes<HTMLElement> & {
as?: ComponentType | 'ul' | 'form'; as?: ComponentType | 'ul' | 'label' | 'form';
space?: keyof typeof gapClasses; space?: keyof typeof gapClasses;
alignItems?: 'start' | 'center' | 'stretch'; alignItems?: 'start' | 'center' | 'stretch';
justifyContent?: 'start' | 'center' | 'end' | 'between'; justifyContent?: 'start' | 'center' | 'end' | 'between';

View File

@@ -11,7 +11,8 @@ export type HotkeyAction =
| 'sidebar.focus' | 'sidebar.focus'
| 'urlBar.focus' | 'urlBar.focus'
| 'environmentEditor.toggle' | 'environmentEditor.toggle'
| 'hotkeys.showHelp'; | 'hotkeys.showHelp'
| 'settings.show';
const hotkeys: Record<HotkeyAction, string[]> = { const hotkeys: Record<HotkeyAction, string[]> = {
'request.send': ['CmdCtrl+Enter', 'CmdCtrl+r'], 'request.send': ['CmdCtrl+Enter', 'CmdCtrl+r'],
@@ -22,6 +23,7 @@ const hotkeys: Record<HotkeyAction, string[]> = {
'urlBar.focus': ['CmdCtrl+l'], 'urlBar.focus': ['CmdCtrl+l'],
'environmentEditor.toggle': ['CmdCtrl+e'], 'environmentEditor.toggle': ['CmdCtrl+e'],
'hotkeys.showHelp': ['CmdCtrl+/'], 'hotkeys.showHelp': ['CmdCtrl+/'],
'settings.show': ['CmdCtrl+,'],
}; };
const hotkeyLabels: Record<HotkeyAction, string> = { const hotkeyLabels: Record<HotkeyAction, string> = {
@@ -32,7 +34,8 @@ const hotkeyLabels: Record<HotkeyAction, string> = {
'sidebar.focus': 'Focus Sidebar', 'sidebar.focus': 'Focus Sidebar',
'urlBar.focus': 'Focus URL', 'urlBar.focus': 'Focus URL',
'environmentEditor.toggle': 'Edit Environments', 'environmentEditor.toggle': 'Edit Environments',
'hotkeys.showHelp': 'Show Hotkeys', 'hotkeys.showHelp': 'Show Keyboard Shortcuts',
'settings.show': 'Open Settings',
}; };
export const hotkeyActions: HotkeyAction[] = Object.keys(hotkeys) as (keyof typeof hotkeys)[]; export const hotkeyActions: HotkeyAction[] = Object.keys(hotkeys) as (keyof typeof hotkeys)[];

View File

@@ -0,0 +1,18 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api';
import type { Settings } from '../lib/models';
export function settingsQueryKey() {
return ['settings'];
}
export function useSettings() {
return (
useQuery({
queryKey: settingsQueryKey(),
queryFn: async () => {
return (await invoke('get_settings')) as Settings;
},
}).data ?? undefined
);
}

View File

@@ -0,0 +1,18 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api';
import type { HttpRequest, Settings } from '../lib/models';
import { requestsQueryKey } from './useRequests';
import { settingsQueryKey } from './useSettings';
export function useUpdateSettings() {
const queryClient = useQueryClient();
return useMutation<void, unknown, Settings>({
mutationFn: async (settings) => {
await invoke('update_settings', { settings });
},
onMutate: async (settings) => {
queryClient.setQueryData<Settings>(settingsQueryKey(), settings);
},
});
}

View File

@@ -9,7 +9,7 @@ export const AUTH_TYPE_NONE = null;
export const AUTH_TYPE_BASIC = 'basic'; export const AUTH_TYPE_BASIC = 'basic';
export const AUTH_TYPE_BEARER = 'bearer'; export const AUTH_TYPE_BEARER = 'bearer';
export type Model = Workspace | HttpRequest | HttpResponse | KeyValue | Environment; export type Model = Settings | Workspace | HttpRequest | HttpResponse | KeyValue | Environment;
export interface BaseModel { export interface BaseModel {
readonly id: string; readonly id: string;
@@ -17,6 +17,13 @@ export interface BaseModel {
readonly updatedAt: string; readonly updatedAt: string;
} }
export interface Settings extends BaseModel {
readonly model: 'settings';
validateCertificates: boolean;
followRedirects: boolean;
theme: string;
}
export interface Workspace extends BaseModel { export interface Workspace extends BaseModel {
readonly model: 'workspace'; readonly model: 'workspace';
name: string; name: string;