mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-11 20:00:29 +01:00
Some cleanup around window creation
This commit is contained in:
@@ -12,7 +12,6 @@ use crate::uri_scheme::handle_uri_scheme;
|
||||
use error::Result as YaakResult;
|
||||
use eventsource_client::{EventParser, SSE};
|
||||
use log::{debug, error, warn};
|
||||
use rand::random;
|
||||
use regex::Regex;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fs::{create_dir_all, File};
|
||||
@@ -20,7 +19,7 @@ use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use std::{fs, panic};
|
||||
use tauri::{AppHandle, Emitter, RunEvent, State, WebviewWindow};
|
||||
use tauri::{is_dev, AppHandle, Emitter, RunEvent, State, WebviewWindow};
|
||||
use tauri::{Listener, Runtime};
|
||||
use tauri::{Manager, WindowEvent};
|
||||
use tauri_plugin_log::fern::colors::ColoredLevelConfig;
|
||||
@@ -68,6 +67,7 @@ use yaak_sse::sse::ServerSentEvent;
|
||||
use yaak_templates::format::format_json;
|
||||
use yaak_templates::{Parser, Tokens};
|
||||
|
||||
mod commands;
|
||||
mod encoding;
|
||||
mod error;
|
||||
mod grpc;
|
||||
@@ -83,15 +83,6 @@ mod uri_scheme;
|
||||
mod window;
|
||||
mod window_menu;
|
||||
|
||||
const DEFAULT_WINDOW_WIDTH: f64 = 1100.0;
|
||||
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")]
|
||||
struct AppMetaData {
|
||||
@@ -1368,15 +1359,6 @@ async fn cmd_update_folder(folder: Folder, w: WebviewWindow) -> Result<Folder, S
|
||||
upsert_folder(&w, folder, &UpdateSource::Window).await.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_write_file_dev(pathname: &str, contents: &str) -> Result<(), String> {
|
||||
if !is_dev() {
|
||||
panic!("Cannot write arbitrary files when not in dev mode");
|
||||
}
|
||||
|
||||
fs::write(pathname, contents).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_delete_folder(w: WebviewWindow, folder_id: &str) -> Result<Folder, String> {
|
||||
delete_folder(&w, folder_id, &UpdateSource::Window).await.map_err(|e| e.to_string())
|
||||
@@ -1632,72 +1614,13 @@ async fn cmd_new_child_window(
|
||||
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 = window::CreateWindowConfig {
|
||||
label: label.as_str(),
|
||||
title,
|
||||
url,
|
||||
inner_size: Some(inner_size),
|
||||
position: Some(position),
|
||||
hide_titlebar: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let child_window = 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();
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
}
|
||||
|
||||
window::create_child_window(&parent_window, url, label, title, inner_size);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_new_main_window(app_handle: AppHandle, url: &str) -> Result<(), String> {
|
||||
create_main_window(&app_handle, url);
|
||||
window::create_main_window(&app_handle, url);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1724,33 +1647,6 @@ async fn cmd_check_for_updates(
|
||||
pub fn run() {
|
||||
#[allow(unused_mut)]
|
||||
let mut builder = tauri::Builder::default()
|
||||
.plugin(
|
||||
Builder::default()
|
||||
.targets([
|
||||
Target::new(TargetKind::Stdout),
|
||||
Target::new(TargetKind::LogDir { file_name: None }),
|
||||
Target::new(TargetKind::Webview),
|
||||
])
|
||||
.level_for("plugin_runtime", log::LevelFilter::Info)
|
||||
.level_for("cookie_store", log::LevelFilter::Info)
|
||||
.level_for("eventsource_client::event_parser", log::LevelFilter::Info)
|
||||
.level_for("h2", log::LevelFilter::Info)
|
||||
.level_for("hyper", log::LevelFilter::Info)
|
||||
.level_for("hyper_util", log::LevelFilter::Info)
|
||||
.level_for("hyper_rustls", log::LevelFilter::Info)
|
||||
.level_for("reqwest", log::LevelFilter::Info)
|
||||
.level_for("sqlx", log::LevelFilter::Warn)
|
||||
.level_for("tao", log::LevelFilter::Info)
|
||||
.level_for("tokio_util", log::LevelFilter::Info)
|
||||
.level_for("tonic", log::LevelFilter::Info)
|
||||
.level_for("tower", log::LevelFilter::Info)
|
||||
.level_for("tracing", log::LevelFilter::Warn)
|
||||
.level_for("swc_ecma_codegen", log::LevelFilter::Off)
|
||||
.level_for("swc_ecma_transforms_base", log::LevelFilter::Off)
|
||||
.with_colors(ColoredLevelConfig::default())
|
||||
.level(if is_dev() { log::LevelFilter::Debug } else { log::LevelFilter::Info })
|
||||
.build(),
|
||||
)
|
||||
.plugin(
|
||||
Builder::default()
|
||||
.targets([
|
||||
@@ -1905,7 +1801,6 @@ pub fn run() {
|
||||
cmd_update_settings,
|
||||
cmd_update_workspace,
|
||||
cmd_update_workspace_meta,
|
||||
cmd_write_file_dev,
|
||||
])
|
||||
.register_uri_scheme_protocol("yaak", handle_uri_scheme)
|
||||
.build(tauri::generate_context!())
|
||||
@@ -1913,7 +1808,7 @@ pub fn run() {
|
||||
.run(|app_handle, event| {
|
||||
match event {
|
||||
RunEvent::Ready => {
|
||||
let w = create_main_window(app_handle, "/");
|
||||
let w = window::create_main_window(app_handle, "/");
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let info = history::store_launch_history(&w).await;
|
||||
debug!("Launched Yaak {:?}", info);
|
||||
@@ -1973,45 +1868,6 @@ pub fn run() {
|
||||
});
|
||||
}
|
||||
|
||||
fn is_dev() -> bool {
|
||||
#[cfg(dev)]
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#[cfg(not(dev))]
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
fn create_main_window(handle: &AppHandle, url: &str) -> WebviewWindow {
|
||||
let mut counter = 0;
|
||||
let label = loop {
|
||||
let label = format!("{MAIN_WINDOW_PREFIX}{counter}");
|
||||
match handle.webview_windows().get(label.as_str()) {
|
||||
None => break Some(label),
|
||||
Some(_) => counter += 1,
|
||||
}
|
||||
}
|
||||
.expect("Failed to generate label for new window");
|
||||
|
||||
let config = window::CreateWindowConfig {
|
||||
url,
|
||||
label: label.as_str(),
|
||||
title: "Yaak",
|
||||
inner_size: Some((DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT)),
|
||||
position: Some((
|
||||
// Offset by random amount so it's easier to differentiate
|
||||
100.0 + random::<f64>() * 20.0,
|
||||
100.0 + random::<f64>() * 20.0,
|
||||
)),
|
||||
hide_titlebar: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
window::create_window(handle, config)
|
||||
}
|
||||
|
||||
async fn get_update_mode(h: &AppHandle) -> UpdateMode {
|
||||
let settings = get_or_create_settings(h).await;
|
||||
UpdateMode::new(settings.update_channel.as_str())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::MAIN_WINDOW_PREFIX;
|
||||
use crate::window::MAIN_WINDOW_PREFIX;
|
||||
use hex_color::HexColor;
|
||||
use log::warn;
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::window_menu::app_menu;
|
||||
use crate::{DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_WIDTH, MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH};
|
||||
use log::{info, warn};
|
||||
use rand::random;
|
||||
use std::process::exit;
|
||||
use tauri::{
|
||||
AppHandle, Emitter, LogicalSize, Manager, Runtime, WebviewUrl, WebviewWindow, WindowEvent,
|
||||
@@ -8,6 +8,15 @@ use tauri::{
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
const DEFAULT_WINDOW_WIDTH: f64 = 1100.0;
|
||||
const DEFAULT_WINDOW_HEIGHT: f64 = 600.0;
|
||||
|
||||
const MIN_WINDOW_WIDTH: f64 = 300.0;
|
||||
const MIN_WINDOW_HEIGHT: f64 = 300.0;
|
||||
|
||||
pub(crate) const MAIN_WINDOW_PREFIX: &str = "main_";
|
||||
const OTHER_WINDOW_PREFIX: &str = "other_";
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct CreateWindowConfig<'s> {
|
||||
pub url: &'s str,
|
||||
@@ -161,3 +170,95 @@ pub(crate) fn create_window<R: Runtime>(
|
||||
|
||||
win
|
||||
}
|
||||
|
||||
pub(crate) fn create_main_window(handle: &AppHandle, url: &str) -> WebviewWindow {
|
||||
let mut counter = 0;
|
||||
let label = loop {
|
||||
let label = format!("{MAIN_WINDOW_PREFIX}{counter}");
|
||||
match handle.webview_windows().get(label.as_str()) {
|
||||
None => break Some(label),
|
||||
Some(_) => counter += 1,
|
||||
}
|
||||
}
|
||||
.expect("Failed to generate label for new window");
|
||||
|
||||
let config = CreateWindowConfig {
|
||||
url,
|
||||
label: label.as_str(),
|
||||
title: "Yaak",
|
||||
inner_size: Some((DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT)),
|
||||
position: Some((
|
||||
// Offset by random amount so it's easier to differentiate
|
||||
100.0 + random::<f64>() * 20.0,
|
||||
100.0 + random::<f64>() * 20.0,
|
||||
)),
|
||||
hide_titlebar: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
create_window(handle, config)
|
||||
}
|
||||
|
||||
pub(crate) fn create_child_window(parent_window: &WebviewWindow, url: &str, label: &str, title: &str, inner_size: (f64, f64)) -> WebviewWindow {
|
||||
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: Some(inner_size),
|
||||
position: Some(position),
|
||||
hide_titlebar: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
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();
|
||||
};
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
}
|
||||
|
||||
child_window
|
||||
}
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
import { open } from '@tauri-apps/plugin-dialog';
|
||||
import React, { useState } from 'react';
|
||||
import { useLocalStorage } from 'react-use';
|
||||
import { capitalize } from '../../lib/capitalize';
|
||||
import { invokeCmd } from '../../lib/tauri';
|
||||
import { getThemes } from '../../lib/theme/themes';
|
||||
import { yaakDark } from '../../lib/theme/themes/yaak';
|
||||
import { getThemeCSS } from '../../lib/theme/window';
|
||||
import { Banner } from '../core/Banner';
|
||||
import { Button } from '../core/Button';
|
||||
import {Editor} from "../core/Editor/Editor";
|
||||
import type { IconProps } from '../core/Icon';
|
||||
import { Icon } from '../core/Icon';
|
||||
import { IconButton } from '../core/IconButton';
|
||||
import { InlineCode } from '../core/InlineCode';
|
||||
import { Input } from '../core/Input';
|
||||
import { Separator } from '../core/Separator';
|
||||
import { HStack, VStack } from '../core/Stacks';
|
||||
|
||||
const buttonColors = [
|
||||
'primary',
|
||||
'secondary',
|
||||
'info',
|
||||
'success',
|
||||
'warning',
|
||||
'danger',
|
||||
'default',
|
||||
] as const;
|
||||
|
||||
const icons: IconProps['icon'][] = [
|
||||
'info',
|
||||
'box',
|
||||
'update',
|
||||
'alert_triangle',
|
||||
'arrow_big_right_dash',
|
||||
'download',
|
||||
'copy',
|
||||
'magic_wand',
|
||||
'settings',
|
||||
'trash',
|
||||
'sparkles',
|
||||
'pencil',
|
||||
'paste',
|
||||
'search',
|
||||
'send_horizontal',
|
||||
];
|
||||
|
||||
const themes = getThemes();
|
||||
|
||||
export function SettingsDesign() {
|
||||
const [exportDir, setExportDir] = useLocalStorage<string | null>('theme_export_dir', null);
|
||||
const [loadingExport, setLoadingExport] = useState<boolean>(false);
|
||||
|
||||
const saveThemes = () => {
|
||||
setLoadingExport(true);
|
||||
setTimeout(async () => {
|
||||
const allThemesCSS = themes.themes.map(getThemeCSS).join('\n\n');
|
||||
const coreThemeCSS = [yaakDark].map(getThemeCSS).join('\n\n');
|
||||
|
||||
try {
|
||||
await invokeCmd('cmd_write_file_dev', {
|
||||
pathname: exportDir + '/themes-all.css',
|
||||
contents: allThemesCSS,
|
||||
});
|
||||
await invokeCmd('cmd_write_file_dev', {
|
||||
pathname: exportDir + '/themes-slim.css',
|
||||
contents: coreThemeCSS,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('FAILED', err);
|
||||
}
|
||||
setLoadingExport(false);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-2 flex flex-col gap-3">
|
||||
<VStack space={2}>
|
||||
<InlineCode>{exportDir}</InlineCode>
|
||||
<HStack space={2}>
|
||||
<Button
|
||||
size="sm"
|
||||
color="secondary"
|
||||
variant="border"
|
||||
onClick={() => {
|
||||
open({ directory: true }).then(setExportDir);
|
||||
}}
|
||||
>
|
||||
Change Export Dir
|
||||
</Button>
|
||||
<Button
|
||||
disabled={exportDir == null}
|
||||
isLoading={loadingExport}
|
||||
size="sm"
|
||||
color="primary"
|
||||
variant="border"
|
||||
onClick={saveThemes}
|
||||
>
|
||||
Export CSS
|
||||
</Button>
|
||||
</HStack>
|
||||
</VStack>
|
||||
<Separator className="my-6" />
|
||||
<Input
|
||||
label="Field Label"
|
||||
name="demo"
|
||||
placeholder="Placeholder"
|
||||
size="sm"
|
||||
rightSlot={<IconButton title="search" size="xs" className="w-8 m-0.5" icon="search" />}
|
||||
stateKey={null}
|
||||
/>
|
||||
<Editor
|
||||
defaultValue={[
|
||||
'// Demo code editor',
|
||||
'let foo = {',
|
||||
' foo: ("bar" || "baz" ?? \'qux\'),',
|
||||
' baz: [1, 10.2, null, false, true],',
|
||||
'};',
|
||||
].join('\n')}
|
||||
heightMode="auto"
|
||||
language="javascript"
|
||||
stateKey={null}
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{buttonColors.map((c, i) => (
|
||||
<Button key={c} color={c} size="sm" leftSlot={<Icon size="sm" icon={icons[i]!} />}>
|
||||
{capitalize(c).slice(0, 4)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{buttonColors.map((c, i) => (
|
||||
<Button
|
||||
key={c}
|
||||
color={c}
|
||||
variant="border"
|
||||
size="sm"
|
||||
leftSlot={<Icon size="sm" icon={icons[i]!} />}
|
||||
>
|
||||
{capitalize(c).slice(0, 4)}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
{icons.map((v, i) => (
|
||||
<IconButton
|
||||
color={buttonColors[i % buttonColors.length]}
|
||||
title={v}
|
||||
variant="border"
|
||||
size="sm"
|
||||
key={v}
|
||||
icon={v}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Banner color="primary">Primary banner</Banner>
|
||||
<Banner color="secondary">Secondary banner</Banner>
|
||||
<Banner color="danger">Danger banner</Banner>
|
||||
<Banner color="warning">Warning banner</Banner>
|
||||
<Banner color="success">Success banner</Banner>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -77,14 +77,6 @@ export function SettingsGeneral() {
|
||||
]}
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
className="mt-3"
|
||||
checked={false}
|
||||
title="Send Usage Statistics (all tracking was removed in 2025.1.2)"
|
||||
disabled
|
||||
onChange={() => {}}
|
||||
/>
|
||||
|
||||
<Separator className="my-4" />
|
||||
|
||||
<Heading level={2}>
|
||||
|
||||
@@ -78,8 +78,7 @@ type TauriCmd =
|
||||
| 'cmd_update_http_request'
|
||||
| 'cmd_update_settings'
|
||||
| 'cmd_update_workspace'
|
||||
| 'cmd_update_workspace_meta'
|
||||
| 'cmd_write_file_dev';
|
||||
| 'cmd_update_workspace_meta';
|
||||
|
||||
export async function invokeCmd<T>(cmd: TauriCmd, args?: InvokeArgs): Promise<T> {
|
||||
// console.log('RUN COMMAND', cmd, args);
|
||||
|
||||
Reference in New Issue
Block a user