diff options
| author | Brendan Allan <[email protected]> | 2026-02-06 23:03:07 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-02-06 23:03:07 +0800 |
| commit | b7ad8e459cae11ca74d976f6bd2e02559912716a (patch) | |
| tree | 6a14053372260d23834349a6fae4023722cdfbbb /packages/desktop/src | |
| parent | 5a1bf3a9687bd08ffb9c3cab3ffd4ed2346c19da (diff) | |
| download | opencode-b7ad8e459cae11ca74d976f6bd2e02559912716a.tar.gz opencode-b7ad8e459cae11ca74d976f6bd2e02559912716a.zip | |
desktop: add loading window and restructure rust (#12176)
Diffstat (limited to 'packages/desktop/src')
| -rw-r--r-- | packages/desktop/src/bindings.ts | 32 | ||||
| -rw-r--r-- | packages/desktop/src/entry.tsx | 5 | ||||
| -rw-r--r-- | packages/desktop/src/index.tsx | 6 | ||||
| -rw-r--r-- | packages/desktop/src/loading.tsx | 77 | ||||
| -rw-r--r-- | packages/desktop/src/styles.css | 10 |
5 files changed, 125 insertions, 5 deletions
diff --git a/packages/desktop/src/bindings.ts b/packages/desktop/src/bindings.ts index 64d0c113d..46dcb7f44 100644 --- a/packages/desktop/src/bindings.ts +++ b/packages/desktop/src/bindings.ts @@ -1,20 +1,48 @@ // This file has been generated by Tauri Specta. Do not edit this file manually. -import { invoke as __TAURI_INVOKE } from "@tauri-apps/api/core" +import { invoke as __TAURI_INVOKE, Channel } from '@tauri-apps/api/core'; +import * as __TAURI_EVENT from "@tauri-apps/api/event"; /** Commands */ export const commands = { killSidecar: () => __TAURI_INVOKE<void>("kill_sidecar"), installCli: () => __TAURI_INVOKE<string>("install_cli"), - ensureServerReady: () => __TAURI_INVOKE<ServerReadyData>("ensure_server_ready"), + awaitInitialization: (events: Channel) => __TAURI_INVOKE<ServerReadyData>("await_initialization", { events }), + getDefaultServerUrl: () => __TAURI_INVOKE<string | null>("get_default_server_url"), setDefaultServerUrl: (url: string | null) => __TAURI_INVOKE<null>("set_default_server_url", { url }), parseMarkdownCommand: (markdown: string) => __TAURI_INVOKE<string>("parse_markdown_command", { markdown }), }; +/** Events */ +export const events = { + loadingWindowComplete: makeEvent<LoadingWindowComplete>("loading-window-complete"), +}; + /* Types */ +export type InitStep = { phase: "server_waiting" } | { phase: "sqlite_waiting" } | { phase: "done" }; + +export type LoadingWindowComplete = null; + export type ServerReadyData = { url: string, password: string | null, }; +/* Tauri Specta runtime */ +function makeEvent<T>(name: string) { + const base = { + listen: (cb: __TAURI_EVENT.EventCallback<T>) => __TAURI_EVENT.listen(name, cb), + once: (cb: __TAURI_EVENT.EventCallback<T>) => __TAURI_EVENT.once(name, cb), + emit: (payload: T) => __TAURI_EVENT.emit(name, payload) as unknown as (T extends null ? () => Promise<void> : (payload: T) => Promise<void>) + }; + + const fn = (target: import("@tauri-apps/api/webview").Webview | import("@tauri-apps/api/window").Window) => ({ + listen: (cb: __TAURI_EVENT.EventCallback<T>) => target.listen(name, cb), + once: (cb: __TAURI_EVENT.EventCallback<T>) => target.once(name, cb), + emit: (payload: T) => target.emit(name, payload) as unknown as (T extends null ? () => Promise<void> : (payload: T) => Promise<void>) + }); + + return Object.assign(fn, base); +} + diff --git a/packages/desktop/src/entry.tsx b/packages/desktop/src/entry.tsx new file mode 100644 index 000000000..b1c9f13f9 --- /dev/null +++ b/packages/desktop/src/entry.tsx @@ -0,0 +1,5 @@ +if (location.pathname === "/loading") { + import("./loading") +} else { + import("./") +} diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx index 66e86bf52..2b74bbabd 100644 --- a/packages/desktop/src/index.tsx +++ b/packages/desktop/src/index.tsx @@ -21,7 +21,8 @@ import { UPDATER_ENABLED } from "./updater" import { initI18n, t } from "./i18n" import pkg from "../package.json" import "./styles.css" -import { commands } from "./bindings" +import { commands, InitStep } from "./bindings" +import { Channel } from "@tauri-apps/api/core" import { createMenu } from "./menu" const root = document.getElementById("root") @@ -307,7 +308,6 @@ const createPlatform = (password: Accessor<string | null>): Platform => ({ .catch(() => undefined) }, - // @ts-expect-error fetch: (input, init) => { const pw = password() @@ -400,7 +400,7 @@ type ServerReadyData = { url: string; password: string | null } // Gate component that waits for the server to be ready function ServerGate(props: { children: (data: Accessor<ServerReadyData>) => JSX.Element }) { - const [serverData] = createResource(() => commands.ensureServerReady()) + const [serverData] = createResource(() => commands.awaitInitialization(new Channel<InitStep>() as any)) const errorMessage = () => { const error = serverData.error diff --git a/packages/desktop/src/loading.tsx b/packages/desktop/src/loading.tsx new file mode 100644 index 000000000..752cde893 --- /dev/null +++ b/packages/desktop/src/loading.tsx @@ -0,0 +1,77 @@ +import { render } from "solid-js/web" +import { MetaProvider } from "@solidjs/meta" +import "@opencode-ai/app/index.css" +import { Font } from "@opencode-ai/ui/font" +import { Splash } from "@opencode-ai/ui/logo" +import "./styles.css" +import { createSignal, Match, onMount } from "solid-js" +import { commands, events, InitStep } from "./bindings" +import { Channel } from "@tauri-apps/api/core" +import { Switch } from "solid-js" + +const root = document.getElementById("root")! + +render(() => { + let splash!: SVGSVGElement + const [state, setState] = createSignal<InitStep | null>(null) + + const channel = new Channel<InitStep>() + channel.onmessage = (e) => setState(e) + commands.awaitInitialization(channel as any).then(() => { + const currentOpacity = getComputedStyle(splash).opacity + + splash.style.animation = "none" + splash.style.animationPlayState = "paused" + splash.style.opacity = currentOpacity + + requestAnimationFrame(() => { + splash.style.transition = "opacity 0.3s ease" + requestAnimationFrame(() => { + splash.style.opacity = "1" + }) + }) + }) + + return ( + <MetaProvider> + <div class="w-screen h-screen bg-background-base flex items-center justify-center"> + <Font /> + <div class="flex flex-col items-center gap-10"> + <Splash ref={splash} class="h-25 animate-[pulse-splash_2s_ease-in-out_infinite]" /> + <span class="text-text-base"> + <Switch fallback="Just a moment..."> + <Match when={state()?.phase === "done"}> + {(_) => { + onMount(() => { + setTimeout(() => events.loadingWindowComplete.emit(null), 1000) + }) + + return "All done" + }} + </Match> + <Match when={state()?.phase === "sqlite_waiting"}> + {(_) => { + const textItems = [ + "Just a moment...", + "Migrating your database", + "This could take a couple of minutes", + ] + const [textIndex, setTextIndex] = createSignal(0) + + onMount(async () => { + await new Promise((res) => setTimeout(res, 3000)) + setTextIndex(1) + await new Promise((res) => setTimeout(res, 6000)) + setTextIndex(2) + }) + + return <>{textItems[textIndex()]}</> + }} + </Match> + </Switch> + </span> + </div> + </div> + </MetaProvider> + ) +}, root) diff --git a/packages/desktop/src/styles.css b/packages/desktop/src/styles.css index 143a21312..941fb95d7 100644 --- a/packages/desktop/src/styles.css +++ b/packages/desktop/src/styles.css @@ -5,3 +5,13 @@ button#decorum-tb-close, div[data-tauri-decorum-tb] { height: calc(var(--spacing) * 10) !important; } + +@keyframes pulse-splash { + 0%, + 100% { + opacity: 0.1; + } + 50% { + opacity: 0.3; + } +} |
