diff --git a/Cargo.lock b/Cargo.lock index b7dd8c025..c3518961e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,6 +206,7 @@ dependencies = [ "tauri-plugin-prevent-default", "tokenizers", "tokio", + "url", "windows 0.61.3", ] diff --git a/apps/stage-tamagotchi/src-tauri/Cargo.toml b/apps/stage-tamagotchi/src-tauri/Cargo.toml index 761d3bcb8..73ddd4c37 100644 --- a/apps/stage-tamagotchi/src-tauri/Cargo.toml +++ b/apps/stage-tamagotchi/src-tauri/Cargo.toml @@ -47,6 +47,7 @@ rubato = "0.16.2" byteorder = "1.5.0" clap = { version = "4.5.40", features = ["derive"] } tokenizers = "0.21.2" +url = "2.5.4" [target.'cfg(target_os = "macos")'.dependencies] objc2 = "0.6.1" diff --git a/apps/stage-tamagotchi/src-tauri/src/app_windows/chat.rs b/apps/stage-tamagotchi/src-tauri/src/app_windows/chat.rs index fb54e7f9b..fd7af2fce 100644 --- a/apps/stage-tamagotchi/src-tauri/src/app_windows/chat.rs +++ b/apps/stage-tamagotchi/src-tauri/src/app_windows/chat.rs @@ -4,7 +4,7 @@ use std::path::Path; use tauri::TitleBarStyle; use tauri::{WebviewUrl, WebviewWindowBuilder}; -pub fn new_chat_window(app: &tauri::AppHandle) -> Result<(), tauri::Error> { +pub fn new_chat_window(app: &tauri::AppHandle) -> Result { let mut builder = WebviewWindowBuilder::new( app, "chat", @@ -28,6 +28,5 @@ pub fn new_chat_window(app: &tauri::AppHandle) -> Result<(), tauri::Error> { builder = builder.traffic_light_position(tauri::LogicalPosition::new(14.0, 20.0)); } - builder.build()?; - Ok(()) + return builder.build(); } diff --git a/apps/stage-tamagotchi/src-tauri/src/app_windows/settings.rs b/apps/stage-tamagotchi/src-tauri/src/app_windows/settings.rs index 675c5424e..1c3eb9266 100644 --- a/apps/stage-tamagotchi/src-tauri/src/app_windows/settings.rs +++ b/apps/stage-tamagotchi/src-tauri/src/app_windows/settings.rs @@ -4,7 +4,7 @@ use std::path::Path; use tauri::TitleBarStyle; use tauri::{WebviewUrl, WebviewWindowBuilder}; -pub fn new_settings_window(app: &tauri::AppHandle) -> Result<(), tauri::Error> { +pub fn new_settings_window(app: &tauri::AppHandle) -> Result { let mut builder = WebviewWindowBuilder::new( app, "settings", @@ -28,6 +28,5 @@ pub fn new_settings_window(app: &tauri::AppHandle) -> Result<(), tauri::Error> { builder = builder.traffic_light_position(tauri::LogicalPosition::new(14.0, 20.0)); } - builder.build()?; - Ok(()) + return builder.build(); } diff --git a/apps/stage-tamagotchi/src-tauri/src/lib.rs b/apps/stage-tamagotchi/src-tauri/src/lib.rs index 709bb0a6d..660c28e5e 100644 --- a/apps/stage-tamagotchi/src-tauri/src/lib.rs +++ b/apps/stage-tamagotchi/src-tauri/src/lib.rs @@ -1,4 +1,4 @@ -use std::{sync::atomic::Ordering, time::Duration}; +use std::{str::FromStr, sync::atomic::Ordering, time::Duration}; use log::info; use tauri::{ @@ -126,6 +126,42 @@ async fn load_models(window: tauri::Window) -> Result<(), String> { Ok(()) } +#[tauri::command] +async fn open_route_in_window( + window: tauri::Window, + route: String, + window_label: String, +) -> Result<(), String> { + let app = window.app_handle(); + + let target_window = match window_label.as_str() { + "chat" => match app.get_webview_window("chat") { + Some(window) => window, + None => app_windows::chat::new_chat_window(app) + .map_err(|e| format!("Failed to create chat window: {}", e))?, + }, + "settings" => match app.get_webview_window("settings") { + Some(window) => window, + None => app_windows::settings::new_settings_window(app) + .map_err(|e| format!("Failed to create settings window: {}", e))?, + }, + _ => { + return Err(format!("Unknown window label: {}", window_label)); + }, + }; + + let mut current_url = target_window + .url() + .map_err(|e| format!("Failed to get current URL: {}", e))?; + let route: String = "/".to_string() + route.trim_start_matches('/'); + current_url.set_fragment(Some(route.to_string().as_str())); + + let _ = target_window.show(); + let _ = target_window.navigate(current_url); + + Ok(()) +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] #[allow(clippy::missing_panics_doc)] pub fn run() { @@ -238,6 +274,7 @@ pub fn run() { start_click_through, stop_click_through, load_models, + open_route_in_window, ]) .build(tauri::generate_context!()) .expect("error while building tauri application") diff --git a/apps/stage-tamagotchi/src/components/Tauri/WindowLink.vue b/apps/stage-tamagotchi/src/components/Tauri/WindowLink.vue new file mode 100644 index 000000000..7b114b2c3 --- /dev/null +++ b/apps/stage-tamagotchi/src/components/Tauri/WindowLink.vue @@ -0,0 +1,23 @@ + + + diff --git a/apps/stage-tamagotchi/src/components/Widgets/ResourceStatusIsland/LoadingProgress.vue b/apps/stage-tamagotchi/src/components/Widgets/ResourceStatusIsland/LoadingProgress.vue index 6ece60564..5d01cfce7 100644 --- a/apps/stage-tamagotchi/src/components/Widgets/ResourceStatusIsland/LoadingProgress.vue +++ b/apps/stage-tamagotchi/src/components/Widgets/ResourceStatusIsland/LoadingProgress.vue @@ -3,6 +3,8 @@ import type { ProgressInfo } from '../../../stores/resources' import { computed } from 'vue' +import WindowLink from '../../Tauri/WindowLink.vue' + const props = defineProps<{ progressInfo: ProgressInfo }>() @@ -25,7 +27,7 @@ const totalProgress = computed(() => { diff --git a/apps/stage-tamagotchi/src/composables/runtime.ts b/apps/stage-tamagotchi/src/composables/runtime.ts index ab371c02d..c6c41e38e 100644 --- a/apps/stage-tamagotchi/src/composables/runtime.ts +++ b/apps/stage-tamagotchi/src/composables/runtime.ts @@ -1,5 +1,5 @@ import { computedAsync } from '@vueuse/core' -import { onMounted, ref } from 'vue' +import { computed, ref } from 'vue' async function getTauri() { try { @@ -11,27 +11,25 @@ async function getTauri() { } } -async function getTauriOSPluginInternal() { - const os = await import('@tauri-apps/plugin-os') - return os -} - export function useAppRuntime() { - const isTauri = ref(false) + const isInitialized = ref(false) const platform = computedAsync(async () => { - if (!isTauri.value) { - return 'web' + const res = (await getTauri())?.platform?.() || 'web' + if (!isInitialized.value) { + isInitialized.value = true } - return (await getTauriOSPluginInternal())?.platform?.() || 'web' - }) + return res + }, 'web') - onMounted(async () => { - isTauri.value = (await getTauri()) != null + const isTauri = computed(() => { + return platform.value !== 'web' }) return { platform, + isInitialized, + isTauri, } } diff --git a/apps/stage-tamagotchi/src/composables/tauri.ts b/apps/stage-tamagotchi/src/composables/tauri.ts index b61041c30..1013f8cb4 100644 --- a/apps/stage-tamagotchi/src/composables/tauri.ts +++ b/apps/stage-tamagotchi/src/composables/tauri.ts @@ -7,7 +7,7 @@ import { computedAsync, until } from '@vueuse/core' import { useAppRuntime } from './runtime' async function untilNoError(fn: () => Promise, onError?: (err?: unknown | null) => void): Promise { - const fnRetry = withRetry(fn, { retryDelay: 5000, retry: Number.MAX_SAFE_INTEGER - 2, onError }) + const fnRetry = withRetry(fn, { retryDelay: 5000, retry: 5, onError }) return await fnRetry() } @@ -54,7 +54,7 @@ export interface AiriTamagotchiEvents extends Events { } export function useTauriEvent() { - const { platform } = useAppRuntime() + const { platform, isInitialized } = useAppRuntime() const tauriEventApi = computedAsync(() => { if (platform.value !== 'web') { @@ -63,6 +63,8 @@ export function useTauriEvent() { }) async function _listen(event: E, callback: EventCallback) { + await until(isInitialized).toBeTruthy() + if (platform.value === 'web') { return () => {} } @@ -106,6 +108,13 @@ export interface InvokeMethods { load_models: { args: undefined, options: undefined, returns: void } stop_click_through: { args: undefined, options: undefined, returns: void } start_click_through: { args: undefined, options: undefined, returns: void } + + // WindowLink.vue + open_route_in_window: { + args: { route: string, windowLabel?: string } | undefined + options: undefined + returns: void + } } interface InvokeMethodShape { @@ -115,7 +124,7 @@ interface InvokeMethodShape { } export function useTauriCore = InvokeMethods>() { - const { platform } = useAppRuntime() + const { platform, isInitialized } = useAppRuntime() const tauriCoreApi = computedAsync(() => { if (platform.value !== 'web') { @@ -123,15 +132,15 @@ export function useTauriCore = In } }) - async function invoke< - C extends keyof IM, - >( + async function invoke( command: C, args?: IM[C]['args'], options?: IM[C]['options'], ): Promise { + await until(isInitialized).toBeTruthy() + if (platform.value === 'web') { - console.warn(`Attempted to invoke Tauri command "${String(command)}" in web platform, however, currently we are not in a Tauri environment.`) + console.warn(`Attempted to invoke Tauri command "${String(command)}" in web platform`) return } diff --git a/apps/stage-tamagotchi/src/pages/index.vue b/apps/stage-tamagotchi/src/pages/index.vue index 7afe8315f..803f48d46 100644 --- a/apps/stage-tamagotchi/src/pages/index.vue +++ b/apps/stage-tamagotchi/src/pages/index.vue @@ -4,12 +4,13 @@ import type { AiriTamagotchiEvents, Point, WindowFrame } from '../composables/ta import { WidgetStage } from '@proj-airi/stage-ui/components' import { useMcpStore } from '@proj-airi/stage-ui/stores' import { connectServer } from '@proj-airi/tauri-plugin-mcp' +import { invoke } from '@tauri-apps/api/core' import { storeToRefs } from 'pinia' import { computed, onMounted, onUnmounted, ref } from 'vue' import ResourceStatusIsland from '../components/Widgets/ResourceStatusIsland/index.vue' import { useAppRuntime } from '../composables/runtime' -import { useTauriCore, useTauriEvent } from '../composables/tauri' +import { useTauriEvent } from '../composables/tauri' import { useWindowShortcuts } from '../composables/window-shortcuts' import { useResourcesStore } from '../stores/resources' import { useWindowControlStore } from '../stores/window-controls' @@ -21,7 +22,6 @@ const { platform } = useAppRuntime() const windowStore = useWindowControlStore() const mcpStore = useMcpStore() const { listen } = useTauriEvent() -const { invoke } = useTauriCore() const isCursorInside = ref(false) const { connected, serverCmd, serverArgs } = storeToRefs(mcpStore) diff --git a/apps/stage-tamagotchi/src/utils/windows.ts b/apps/stage-tamagotchi/src/utils/windows.ts index d50a6417c..bdb3bd2d2 100644 --- a/apps/stage-tamagotchi/src/utils/windows.ts +++ b/apps/stage-tamagotchi/src/utils/windows.ts @@ -1,11 +1,9 @@ -import { useTauriCore } from '../composables/tauri' +import { invoke } from '@tauri-apps/api/core' export async function startClickThrough() { - const { invoke } = useTauriCore() await invoke('start_click_through') } export async function stopClickThrough() { - const { invoke } = useTauriCore() await invoke('stop_click_through') }