Refactor window handling to ensure focus after showing

- Updated various components to use promise chaining with `show()` and `setFocus()` for better window management.
- Introduced `data.win` in multiple places to streamline access to the current webview window.
- Enhanced splashscreen and app layout handling to improve user experience by ensuring the window is focused after being shown.
This commit is contained in:
Huakun Shen 2025-03-28 06:47:51 -04:00
parent 790e457b8a
commit 57117b4f3e
No known key found for this signature in database
14 changed files with 47 additions and 38 deletions

View File

@ -24,6 +24,7 @@
"core:event:default", "core:event:default",
"core:window:default", "core:window:default",
"core:window:allow-set-size", "core:window:allow-set-size",
"core:window:allow-set-enabled",
"core:window:allow-start-dragging", "core:window:allow-start-dragging",
"core:window:allow-set-focus", "core:window:allow-set-focus",
"core:window:allow-toggle-maximize", "core:window:allow-toggle-maximize",

View File

@ -411,7 +411,7 @@ export const rawBuiltinCmds: BuiltinCmd[] = [
visible: false visible: false
}) })
setTimeout(() => { setTimeout(() => {
window.show() window.show().then(() => window.setFocus())
}, 2_000) }, 2_000)
} }
}, },

View File

@ -48,8 +48,7 @@ export async function registerAppHotkey(hotkeyStr: string) {
mainWin.setFocus() mainWin.setFocus()
} }
} else { } else {
mainWin.show() mainWin.show().then(() => mainWin.setFocus())
mainWin.setFocus()
} }
} }
}) })

View File

@ -97,7 +97,7 @@ export async function globalKeyDownHandler(e: KeyboardEvent) {
await appWin.hide() await appWin.hide()
location.reload() location.reload()
setTimeout(() => { setTimeout(() => {
appWin.show() appWin.show().then(() => appWin.setFocus())
}, 1_000) }, 1_000)
} }
} }

View File

@ -1,5 +1,12 @@
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"
// Tauri doesn't have a Node.js server to do proper SSR // Tauri doesn't have a Node.js server to do proper SSR
// so we will use adapter-static to prerender the app (SSG) // so we will use adapter-static to prerender the app (SSG)
// See: https://v2.tauri.app/start/frontend/sveltekit/ for more info // See: https://v2.tauri.app/start/frontend/sveltekit/ for more info
export const prerender = true export const prerender = true
export const ssr = false export const ssr = false
export const load = () => {
const win = getCurrentWebviewWindow()
return { win }
}

View File

@ -46,7 +46,8 @@
}) })
}) })
let { children } = $props() let { children, data } = $props()
const unlisteners: UnlistenFn[] = [] const unlisteners: UnlistenFn[] = []
onDestroy(() => { onDestroy(() => {
unlisteners.forEach((unlistener) => unlistener()) unlisteners.forEach((unlistener) => unlistener())
@ -100,7 +101,7 @@
}) })
) )
} }
getCurrentWebviewWindow().show() data.win.setFocus()
}) })
</script> </script>

View File

@ -1,8 +1,8 @@
import { getExtensionsFolder, IS_IN_TAURI } from "@/constants" import { getExtensionsFolder, IS_IN_TAURI } from "@/constants"
import * as path from "@tauri-apps/api/path"
import { error } from "@tauri-apps/plugin-log" import { error } from "@tauri-apps/plugin-log"
import { setStoreCollectionPath } from "@tauri-store/svelte"
import type { LayoutLoad } from "./$types" import type { LayoutLoad } from "./$types"
import { setStoreCollectionPath } from '@tauri-store/svelte';
import * as path from "@tauri-apps/api/path";
export const load: LayoutLoad = async () => { export const load: LayoutLoad = async () => {
const appDataPath = await path.appDataDir() const appDataPath = await path.appDataDir()

View File

@ -64,12 +64,16 @@
if (splashscreenWin) { if (splashscreenWin) {
splashscreenWin.close() splashscreenWin.close()
} }
win.show() win.show().then(() => win.setFocus())
}) })
win.onFocusChanged(({ payload: focused }) => { win.onFocusChanged(({ payload: focused }) => {
if (focused) { if (focused) {
win.show() win
inputEle?.focus() .show()
.then(() => win.setFocus())
.finally(() => {
inputEle?.focus()
})
} }
}) })
inputEle?.focus() inputEle?.focus()

View File

@ -8,6 +8,8 @@
import * as userInput from "tauri-plugin-user-input-api" import * as userInput from "tauri-plugin-user-input-api"
import { type InputEvent } from "tauri-plugin-user-input-api" import { type InputEvent } from "tauri-plugin-user-input-api"
let { data } = $props()
const SymbolMap = { const SymbolMap = {
Alt: "⎇", Alt: "⎇",
AltGr: "⌥", AltGr: "⌥",
@ -97,10 +99,7 @@
} }
$effect(() => { $effect(() => {
const win = getCurrentWebviewWindow() data.win.show().then(() => data.win.setFocus())
if (win) {
win.show()
}
userInput.setEventTypes([userInput.EventTypeEnum.KeyPress, userInput.EventTypeEnum.KeyRelease]) userInput.setEventTypes([userInput.EventTypeEnum.KeyPress, userInput.EventTypeEnum.KeyRelease])
userInput.startListening((evt: InputEvent) => { userInput.startListening((evt: InputEvent) => {

View File

@ -2,13 +2,12 @@
import { Button } from "@kksh/svelte5" import { Button } from "@kksh/svelte5"
import { Layouts } from "@kksh/ui" import { Layouts } from "@kksh/ui"
import { LogicalSize } from "@tauri-apps/api/dpi" import { LogicalSize } from "@tauri-apps/api/dpi"
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"
import { CircleX } from "lucide-svelte" import { CircleX } from "lucide-svelte"
import { onMount } from "svelte" import { onMount } from "svelte"
import * as clipboard from "tauri-plugin-clipboard-api" import * as clipboard from "tauri-plugin-clipboard-api"
let image = $state<string | null>(null) let image = $state<string | null>(null)
const appWin = getCurrentWebviewWindow() let { data } = $props()
let originalSize = $state<{ width: number; height: number } | null>(null) let originalSize = $state<{ width: number; height: number } | null>(null)
let originalScaleFactor = $state<number | null>(null) let originalScaleFactor = $state<number | null>(null)
let scale = $state<number>(1) let scale = $state<number>(1)
@ -18,13 +17,13 @@
$effect(() => { $effect(() => {
if (currentSize) { if (currentSize) {
appWin.setSize(new LogicalSize(currentSize.width, currentSize.height)) data.win.setSize(new LogicalSize(currentSize.width, currentSize.height))
} }
}) })
async function getWindowSize() { async function getWindowSize() {
const size = await appWin.outerSize() const size = await data.win.outerSize()
const scaleFactor = originalScaleFactor ?? (await appWin.scaleFactor()) const scaleFactor = originalScaleFactor ?? (await data.win.scaleFactor())
const logicalSize = size.toLogical(scaleFactor) const logicalSize = size.toLogical(scaleFactor)
return { logicalSize, scaleFactor } return { logicalSize, scaleFactor }
} }
@ -36,7 +35,7 @@
image = b64 image = b64
}) })
.finally(() => { .finally(() => {
appWin.show() data.win.show().then(() => data.win.setFocus())
}) })
const { logicalSize, scaleFactor } = await getWindowSize() const { logicalSize, scaleFactor } = await getWindowSize()
originalSize = { width: logicalSize.width, height: logicalSize.height } originalSize = { width: logicalSize.width, height: logicalSize.height }
@ -67,13 +66,13 @@
<svelte:window <svelte:window
on:keydown={(e) => { on:keydown={(e) => {
if (e.key === "Escape") { if (e.key === "Escape") {
appWin.close() data.win.close()
} }
}} }}
/> />
<Button size="icon" variant="ghost" class="fixed left-2 top-2" onclick={() => appWin.close()} <Button size="icon" variant="ghost" class="fixed left-2 top-2" onclick={() => data.win.close()}>
><CircleX /></Button <CircleX />
> </Button>
<main class="z-50 h-screen w-screen overflow-hidden" data-tauri-drag-region> <main class="z-50 h-screen w-screen overflow-hidden" data-tauri-drag-region>
{#if image} {#if image}
<img <img

View File

@ -38,7 +38,6 @@
let { data }: { data: PageData } = $props() let { data }: { data: PageData } = $props()
const { loadedExt, url, extPath, extInfoInDB } = data const { loadedExt, url, extPath, extInfoInDB } = data
let extSpawnedProcesses = $state<number[]>([]) let extSpawnedProcesses = $state<number[]>([])
const appWin = getCurrentWindow()
let iframeRef: HTMLIFrameElement let iframeRef: HTMLIFrameElement
let uiControl = $state<{ let uiControl = $state<{
iframeLoaded: boolean iframeLoaded: boolean
@ -65,7 +64,7 @@
if (isInMainWindow()) { if (isInMainWindow()) {
goto(i18n.resolveRoute("/app/")) goto(i18n.resolveRoute("/app/"))
} else { } else {
appWin.close() data.win.close()
} }
}, },
hideBackButton: async () => { hideBackButton: async () => {
@ -131,7 +130,7 @@
}, },
getSpawnedProcesses: () => Promise.resolve(extSpawnedProcesses), getSpawnedProcesses: () => Promise.resolve(extSpawnedProcesses),
paste: async () => { paste: async () => {
await appWin.hide() await data.win.hide()
await sleep(200) await sleep(200)
return paste() return paste()
} }
@ -155,7 +154,7 @@
if (isInMainWindow()) { if (isInMainWindow()) {
goHome() goHome()
} else { } else {
appWin.close() data.win.close()
} }
} }
@ -170,7 +169,7 @@
onMount(() => { onMount(() => {
appState.setFullScreenLoading(true) appState.setFullScreenLoading(true)
setTimeout(() => { setTimeout(() => {
appWin.show() data.win.setFocus()
}, 200) }, 200)
if (iframeRef?.contentWindow) { if (iframeRef?.contentWindow) {
const io = new IframeParentIO(iframeRef.contentWindow) const io = new IframeParentIO(iframeRef.contentWindow)
@ -196,7 +195,7 @@
}) })
onDestroy(() => { onDestroy(() => {
winExtMap.unregisterExtensionFromWindow(appWin.label) winExtMap.unregisterExtensionFromWindow(data.win.label)
}) })
</script> </script>
@ -209,7 +208,7 @@
onclick={onBackBtnClicked} onclick={onBackBtnClicked}
style={`${positionToCssStyleString(uiControl.backBtnPosition)}`} style={`${positionToCssStyleString(uiControl.backBtnPosition)}`}
> >
{#if appWin.label === "main"} {#if data.win.label === "main"}
<ArrowLeftIcon class="w-4" /> <ArrowLeftIcon class="w-4" />
{:else} {:else}
<XIcon class="w-4" /> <XIcon class="w-4" />

View File

@ -1,5 +1,6 @@
import { KunkunIframeExtParams } from "@/cmds/ext" import { KunkunIframeExtParams } from "@/cmds/ext"
import { i18n } from "@/i18n" import { i18n } from "@/i18n"
import { appState } from "@/stores/appState"
import { db, unregisterExtensionWindow } from "@kksh/api/commands" import { db, unregisterExtensionWindow } from "@kksh/api/commands"
import type { Ext as ExtInfoInDB, ExtPackageJsonExtra } from "@kksh/api/models" import type { Ext as ExtInfoInDB, ExtPackageJsonExtra } from "@kksh/api/models"
import { loadExtensionManifestFromDisk } from "@kksh/extension" import { loadExtensionManifestFromDisk } from "@kksh/extension"
@ -11,7 +12,6 @@ import { toast } from "svelte-sonner"
import * as v from "valibot" import * as v from "valibot"
import { z } from "zod" import { z } from "zod"
import type { PageLoad } from "./$types" import type { PageLoad } from "./$types"
import { appState } from "@/stores/appState"
export const load: PageLoad = async ({ export const load: PageLoad = async ({
url, url,

View File

@ -310,7 +310,7 @@
onMount(async () => { onMount(async () => {
setTimeout(() => { setTimeout(() => {
appState.setLoadingBar(true) appState.setLoadingBar(true)
appWin.show() appWin.show().then(() => appWin.setFocus())
}, 100) }, 100)
unlistenRefreshWorkerExt = await listenToRefreshDevExt(() => { unlistenRefreshWorkerExt = await listenToRefreshDevExt(() => {
debug("Refreshing Worker Extension") debug("Refreshing Worker Extension")

View File

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import { Layouts } from "@kksh/ui" import { Layouts } from "@kksh/ui"
import { getCurrentWindow } from "@tauri-apps/api/window"
import { onMount } from "svelte" import { onMount } from "svelte"
onMount(async () => { let { data } = $props()
const mainWin = await getCurrentWindow()
mainWin.show() onMount(() => {
data.win.show().then(() => data.win.setFocus())
}) })
</script> </script>