Make settings menu a regular window (#113)

This commit is contained in:
Gregory Schier
2024-09-25 12:52:12 -07:00
committed by GitHub
parent 2be45d6101
commit b19748c42e
13 changed files with 201 additions and 111 deletions

View File

@@ -1,5 +1,5 @@
# Generated by Cargo
# will have compiled files and executables
/target/
target/
vendored

View File

@@ -35,12 +35,12 @@
"core:window:allow-is-fullscreen",
"core:window:allow-maximize",
"core:window:allow-minimize",
"core:window:allow-toggle-maximize",
"core:window:allow-set-decorations",
"core:window:allow-set-title",
"core:window:allow-start-dragging",
"core:window:allow-unmaximize",
"core:window:allow-theme",
"core:window:allow-toggle-maximize",
"core:window:allow-unmaximize",
"clipboard-manager:allow-read-text",
"clipboard-manager:allow-write-text"
]

View File

@@ -1 +1 @@
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["core:event:allow-emit","core:event:allow-listen","core:event:allow-unlisten","os:allow-os-type","clipboard-manager:allow-clear","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"shell:allow-open","core:webview:allow-set-webview-zoom","core:window:allow-close","core:window:allow-is-fullscreen","core:window:allow-maximize","core:window:allow-minimize","core:window:allow-toggle-maximize","core:window:allow-set-decorations","core:window:allow-set-title","core:window:allow-start-dragging","core:window:allow-unmaximize","core:window:allow-theme","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text"]}}
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["core:event:allow-emit","core:event:allow-listen","core:event:allow-unlisten","os:allow-os-type","clipboard-manager:allow-clear","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"shell:allow-open","core:webview:allow-set-webview-zoom","core:window:allow-close","core:window:allow-is-fullscreen","core:window:allow-maximize","core:window:allow-minimize","core:window:allow-set-decorations","core:window:allow-set-title","core:window:allow-start-dragging","core:window:allow-theme","core:window:allow-toggle-maximize","core:window:allow-unmaximize","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text"]}}

View File

@@ -2,13 +2,13 @@ extern crate core;
#[cfg(target_os = "macos")]
extern crate objc;
use std::collections::{BTreeMap};
use std::fs;
use std::collections::BTreeMap;
use std::fs::{create_dir_all, read_to_string, File};
use std::path::PathBuf;
use std::process::exit;
use std::str::FromStr;
use std::time::Duration;
use std::{fs, panic};
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
@@ -83,7 +83,9 @@ const DEFAULT_WINDOW_HEIGHT: f64 = 600.0;
const MIN_WINDOW_WIDTH: f64 = 300.0;
const MIN_WINDOW_HEIGHT: f64 = 300.0;
const MAIN_WINDOW_PREFIX: &str = "main_";
const OTHER_WINDOW_PREFIX: &str = "other_";
#[derive(serde::Serialize)]
#[serde(default, rename_all = "camelCase")]
@@ -1649,19 +1651,83 @@ async fn cmd_list_workspaces(w: WebviewWindow) -> Result<Vec<Workspace>, String>
}
#[tauri::command]
async fn cmd_new_window(app_handle: AppHandle, url: &str) -> Result<(), String> {
create_window(&app_handle, url);
async fn cmd_new_child_window(
parent_window: WebviewWindow,
url: &str,
label: &str,
title: &str,
inner_size: (f64, f64),
) -> Result<(), String> {
let app_handle = parent_window.app_handle();
let label = format!("{OTHER_WINDOW_PREFIX}_{label}");
let scale_factor = parent_window.scale_factor().unwrap();
let current_pos = parent_window
.inner_position()
.unwrap()
.to_logical::<f64>(scale_factor);
let current_size = parent_window
.inner_size()
.unwrap()
.to_logical::<f64>(scale_factor);
// Position the new window in the middle of the parent
let position = (
current_pos.x + current_size.width / 2.0 - inner_size.0 / 2.0,
current_pos.y + current_size.height / 2.0 - inner_size.1 / 2.0,
);
let config = CreateWindowConfig {
label: label.as_str(),
title,
url,
inner_size,
position,
};
let child_window = create_window(&app_handle, config);
// NOTE: These listeners will remain active even when the windows close. Unfortunately,
// there's no way to unlisten to events for now, so we just have to be defensive.
{
let parent_window = parent_window.clone();
let child_window = child_window.clone();
child_window.clone().on_window_event(move |e| match e {
// When the new window is destroyed, bring the other up behind it
WindowEvent::Destroyed => {
if let Some(w) = parent_window.get_webview_window(child_window.label()) {
w.set_focus().unwrap();
}
}
_ => {}
});
}
{
let parent_window = parent_window.clone();
let child_window = child_window.clone();
parent_window.clone().on_window_event(move |e| match e {
// When the parent window is closed, close the child
WindowEvent::CloseRequested { .. } => child_window.destroy().unwrap(),
// When the parent window is focused, bring the child above
WindowEvent::Focused(focus) => {
if *focus {
if let Some(w) = parent_window.get_webview_window(child_window.label()) {
w.set_focus().unwrap();
};
}
}
_ => {}
});
}
Ok(())
}
#[tauri::command]
async fn cmd_new_nested_window(
window: WebviewWindow,
url: &str,
label: &str,
title: &str,
) -> Result<(), String> {
create_nested_window(&window, label, url, title);
async fn cmd_new_main_window(app_handle: AppHandle, url: &str) -> Result<(), String> {
create_main_window(&app_handle, url);
Ok(())
}
@@ -1721,7 +1787,18 @@ pub fn run() {
.build(),
)
.plugin(tauri_plugin_clipboard_manager::init())
.plugin(tauri_plugin_window_state::Builder::default().build())
.plugin(
tauri_plugin_window_state::Builder::default()
.with_denylist(&["ignored"])
.map_label(|label| {
if label.starts_with(OTHER_WINDOW_PREFIX) {
"ignored"
} else {
label
}
})
.build(),
)
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_updater::Builder::default().build())
.plugin(tauri_plugin_dialog::init())
@@ -1810,8 +1887,8 @@ pub fn run() {
cmd_list_plugins,
cmd_list_workspaces,
cmd_metadata,
cmd_new_nested_window,
cmd_new_window,
cmd_new_child_window,
cmd_new_main_window,
cmd_parse_template,
cmd_plugin_info,
cmd_reload_plugins,
@@ -1844,7 +1921,7 @@ pub fn run() {
.run(|app_handle, event| {
match event {
RunEvent::Ready => {
let w = create_window(app_handle, "/");
let w = create_main_window(app_handle, "/");
tauri::async_runtime::spawn(async move {
let info = analytics::track_launch_event(&w).await;
debug!("Launched Yaak {:?}", info);
@@ -1866,7 +1943,9 @@ pub fn run() {
tauri::async_runtime::spawn(async move {
let val: State<'_, Mutex<YaakUpdater>> = h.state();
let update_mode = get_update_mode(&h).await;
_ = val.lock().await.check(&h, update_mode).await;
if let Err(e) = val.lock().await.check(&h, update_mode).await {
warn!("Failed to check for updates {e:?}");
};
});
let h = app_handle.clone();
@@ -1897,47 +1976,31 @@ fn is_dev() -> bool {
}
}
fn create_nested_window(
window: &WebviewWindow,
label: &str,
url: &str,
title: &str,
) -> WebviewWindow {
info!("Create new nested window label={label}");
let mut win_builder = tauri::WebviewWindowBuilder::new(
window,
format!("nested_{}_{}", window.label(), label),
WebviewUrl::App(url.into()),
)
.resizable(true)
.fullscreen(false)
.disable_drag_drop_handler() // Required for frontend Dnd on windows
.title(title)
.parent(&window)
.unwrap()
.min_inner_size(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
.inner_size(DEFAULT_WINDOW_WIDTH * 0.7, DEFAULT_WINDOW_HEIGHT * 0.9);
// Add macOS-only things
#[cfg(target_os = "macos")]
{
win_builder = win_builder
.hidden_title(true)
.title_bar_style(TitleBarStyle::Overlay);
}
// Add non-macOS things
#[cfg(not(target_os = "macos"))]
{
win_builder = win_builder.decorations(false);
}
let win = win_builder.build().expect("failed to build window");
win
fn create_main_window(handle: &AppHandle, url: &str) -> WebviewWindow {
let label = format!("{MAIN_WINDOW_PREFIX}{}", handle.webview_windows().len());
let config = CreateWindowConfig {
url,
label: label.as_str(),
title: "Yaak",
inner_size: (DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT),
position: (
// Offset by random amount so it's easier to differentiate
100.0 + random::<f64>() * 20.0,
100.0 + random::<f64>() * 20.0,
),
};
create_window(handle, config)
}
fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
struct CreateWindowConfig<'s> {
url: &'s str,
label: &'s str,
title: &'s str,
inner_size: (f64, f64),
position: (f64, f64),
}
fn create_window(handle: &AppHandle, config: CreateWindowConfig) -> WebviewWindow {
#[allow(unused_variables)]
let menu = app_menu(handle).unwrap();
@@ -1945,22 +2008,17 @@ fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
#[cfg(not(target_os = "linux"))]
handle.set_menu(menu).expect("Failed to set app menu");
let window_num = handle.webview_windows().len();
let label = format!("{MAIN_WINDOW_PREFIX}{window_num}");
info!("Create new window label={label}");
info!("Create new window label={}", config.label);
let mut win_builder =
tauri::WebviewWindowBuilder::new(handle, label, WebviewUrl::App(url.into()))
tauri::WebviewWindowBuilder::new(handle, config.label, WebviewUrl::App(config.url.into()))
.title(config.title)
.resizable(true)
.fullscreen(false)
.disable_drag_drop_handler() // Required for frontend Dnd on windows
.inner_size(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT)
.position(
// Randomly offset so windows don't stack exactly
100.0 + random::<f64>() * 30.0,
100.0 + random::<f64>() * 30.0,
)
.min_inner_size(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT)
.title(handle.package_info().name.to_string());
.inner_size(config.inner_size.0, config.inner_size.1)
.position(config.position.0, config.position.1)
.min_inner_size(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT);
// Add macOS-only things
#[cfg(target_os = "macos")]
@@ -1977,7 +2035,16 @@ fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
win_builder = win_builder.decorations(false);
}
let win = win_builder.build().expect("failed to build window");
if let Some(w) = handle.webview_windows().get(config.label) {
info!(
"Webview with label {} already exists. Focusing existing",
config.label
);
w.set_focus().unwrap();
return w.to_owned();
}
let win = win_builder.build().unwrap();
let webview_window = win.clone();
win.on_menu_event(move |w, event| {
@@ -1994,10 +2061,13 @@ fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
"zoom_out" => w.emit("zoom_out", true).unwrap(),
"settings" => w.emit("settings", true).unwrap(),
"open_feedback" => {
_ = webview_window
if let Err(e) = webview_window
.app_handle()
.shell()
.open("https://yaak.app/roadmap", None)
.open("https://yaak.app/feedback", None)
{
warn!("Failed to open feedback {e:?}")
}
}
// Commands for development

View File

@@ -2,7 +2,11 @@ use hex_color::HexColor;
use log::warn;
use objc::{msg_send, sel, sel_impl};
use rand::{distributions::Alphanumeric, Rng};
use tauri::{plugin::{Builder, TauriPlugin}, Manager, Runtime, Window, WindowEvent, Emitter, Listener};
use tauri::{
plugin::{Builder, TauriPlugin},
Emitter, Listener, Manager, Runtime, Window, WindowEvent,
};
use crate::MAIN_WINDOW_PREFIX;
const WINDOW_CONTROL_PAD_X: f64 = 13.0;
const WINDOW_CONTROL_PAD_Y: f64 = 18.0;
@@ -127,7 +131,7 @@ fn update_window_theme<R: Runtime>(window: Window<R>, color: HexColor) {
#[cfg(target_os = "macos")]
fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64, label: String) {
if label.starts_with("nested_") {
if !label.starts_with(MAIN_WINDOW_PREFIX) {
return;
}

View File

@@ -1,11 +1,10 @@
import classNames from 'classnames';
import { search } from 'fast-fuzzy';
import type { KeyboardEvent, ReactNode } from 'react';
import { useEffect, useRef, useCallback, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useActiveCookieJar } from '../hooks/useActiveCookieJar';
import { useActiveEnvironment } from '../hooks/useActiveEnvironment';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { useCreateEnvironment } from '../hooks/useCreateEnvironment';
import { useCreateGrpcRequest } from '../hooks/useCreateGrpcRequest';
@@ -17,6 +16,7 @@ import { useEnvironments } from '../hooks/useEnvironments';
import type { HotkeyAction } from '../hooks/useHotKey';
import { useHotKey } from '../hooks/useHotKey';
import { useHttpRequestActions } from '../hooks/useHttpRequestActions';
import { useOpenSettings } from '../hooks/useOpenSettings';
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
import { useRecentRequests } from '../hooks/useRecentRequests';
@@ -28,7 +28,6 @@ import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { useWorkspaces } from '../hooks/useWorkspaces';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { invokeCmd } from '../lib/tauri';
import { CookieDialog } from './CookieDialog';
import { Button } from './core/Button';
import { Heading } from './core/Heading';
@@ -74,11 +73,11 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
const createGrpcRequest = useCreateGrpcRequest();
const createEnvironment = useCreateEnvironment();
const dialog = useDialog();
const workspace = useActiveWorkspace();
const sendRequest = useSendAnyHttpRequest();
const renameRequest = useRenameRequest(activeRequest?.id ?? null);
const deleteRequest = useDeleteRequest(activeRequest?.id ?? null);
const [, setSidebarHidden] = useSidebarHidden();
const openSettings = useOpenSettings();
const workspaceCommands = useMemo<CommandPaletteItem[]>(() => {
const commands: CommandPaletteItem[] = [
@@ -86,14 +85,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
key: 'settings.open',
label: 'Open Settings',
action: 'settings.show',
onSelect: async () => {
if (workspace == null) return;
await invokeCmd('cmd_new_nested_window', {
url: routes.paths.workspaceSettings({ workspaceId: workspace.id }),
label: 'settings',
title: 'Yaak Settings',
});
},
onSelect: openSettings.mutate,
},
{
key: 'app.create',
@@ -195,11 +187,10 @@ export function CommandPalette({ onClose }: { onClose: () => void }) {
deleteRequest.mutate,
dialog,
httpRequestActions,
openSettings.mutate,
renameRequest.mutate,
routes.paths,
sendRequest,
setSidebarHidden,
workspace,
]);
const sortedRequests = useMemo(() => {

View File

@@ -160,9 +160,8 @@ export function GlobalHooks() {
return;
}
const { interfaceScale, interfaceFontSize, editorFontSize } = settings;
const { interfaceScale, editorFontSize } = settings;
getCurrentWebviewWindow().setZoom(interfaceScale).catch(console.error);
document.documentElement.style.setProperty('font-size', `${interfaceFontSize}px`);
document.documentElement.style.setProperty('--editor-font-size', `${editorFontSize}px`);
}, [settings]);

View File

@@ -1,13 +1,11 @@
import { open } from '@tauri-apps/plugin-shell';
import { useRef } from 'react';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useAppInfo } from '../hooks/useAppInfo';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { useCheckForUpdates } from '../hooks/useCheckForUpdates';
import { useExportData } from '../hooks/useExportData';
import { useImportData } from '../hooks/useImportData';
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
import { invokeCmd } from '../lib/tauri';
import { useOpenSettings } from '../hooks/useOpenSettings';
import type { DropdownRef } from './core/Dropdown';
import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
@@ -22,19 +20,9 @@ export function SettingsDropdown() {
const dropdownRef = useRef<DropdownRef>(null);
const dialog = useDialog();
const checkForUpdates = useCheckForUpdates();
const routes = useAppRoutes();
const workspace = useActiveWorkspace();
const openSettings = useOpenSettings();
const showSettings = async () => {
if (!workspace) return;
await invokeCmd('cmd_new_nested_window', {
url: routes.paths.workspaceSettings({ workspaceId: workspace.id }),
label: 'settings',
title: 'Yaak Settings',
});
};
useListenToTauriEvent('settings', showSettings);
useListenToTauriEvent('settings', () => openSettings.mutate());
return (
<Dropdown
@@ -45,7 +33,7 @@ export function SettingsDropdown() {
label: 'Settings',
hotKeyAction: 'settings.show',
leftSlot: <Icon icon="settings" />,
onSelect: showSettings,
onSelect: openSettings.mutate,
},
{
key: 'hotkeys',

15
src-web/font-size.ts Normal file
View File

@@ -0,0 +1,15 @@
// Listen for settings changes, the re-compute theme
import { listen } from '@tauri-apps/api/event';
import type { ModelPayload } from './components/GlobalHooks';
import { getSettings } from './lib/store';
function setFontSizeOnDocument(fontSize: number) {
document.documentElement.style.fontSize = `${fontSize}px`;
}
listen<ModelPayload>('upserted_model', async (event) => {
if (event.payload.model.model !== 'settings') return;
setFontSizeOnDocument(event.payload.model.interfaceFontSize);
}).catch(console.error);
getSettings().then((settings) => setFontSizeOnDocument(settings.interfaceFontSize));

View File

@@ -0,0 +1,22 @@
import { useMutation } from '@tanstack/react-query';
import { invokeCmd } from '../lib/tauri';
import { useActiveWorkspace } from './useActiveWorkspace';
import { useAppRoutes } from './useAppRoutes';
export function useOpenSettings() {
const routes = useAppRoutes();
const workspace = useActiveWorkspace();
return useMutation({
mutationKey: ['open_settings'],
mutationFn: async () => {
if (workspace == null) return;
await invokeCmd('cmd_new_child_window', {
url: routes.paths.workspaceSettings({ workspaceId: workspace.id }),
label: 'settings',
title: 'Yaak Settings',
innerSize: [600, 550],
});
},
});
}

View File

@@ -26,7 +26,7 @@ export function useOpenWorkspace() {
requestId != null
? routes.paths.request({ ...baseArgs, requestId })
: routes.paths.workspace({ ...baseArgs });
await invokeCmd('cmd_new_window', { url: path });
await invokeCmd('cmd_new_main_window', { url: path });
} else {
if (requestId != null) {
routes.navigate('request', { ...baseArgs, requestId });

View File

@@ -27,6 +27,7 @@
<div id="react-portal"></div>
<div id="radix-portal" class="cm-portal"></div>
<script type="module" src="/theme.ts"></script>
<script type="module" src="/font-size.ts"></script>
<script type="module" src="/main.tsx"></script>
</body>
</html>

View File

@@ -51,8 +51,8 @@ type TauriCmd =
| 'cmd_list_plugins'
| 'cmd_list_workspaces'
| 'cmd_metadata'
| 'cmd_new_nested_window'
| 'cmd_new_window'
| 'cmd_new_main_window'
| 'cmd_new_child_window'
| 'cmd_parse_template'
| 'cmd_plugin_info'
| 'cmd_render_template'