fix(stage-tamagotchi): bring back onboarding dialog (#1104)

This commit is contained in:
Rin
2026-03-09 22:06:13 +08:00
committed by GitHub
parent 604341a1e6
commit bed6b475a3
4 changed files with 179 additions and 9 deletions
+9
View File
@@ -31,6 +31,7 @@ import { setupChatWindowReusableFunc } from './windows/chat'
import { setupDevtoolsWindow } from './windows/devtools'
import { setupMainWindow } from './windows/main'
import { setupNoticeWindowManager } from './windows/notice'
import { setupOnboardingWindowManager } from './windows/onboarding'
import { setupSettingsWindowReusableFunc } from './windows/settings'
import { setupWidgetsWindowManager } from './windows/widgets'
@@ -107,6 +108,8 @@ app.whenReady().then(async () => {
const beatSync = injeca.provide('windows:beat-sync', () => setupBeatSync())
const devtoolsMarkdownStressWindow = injeca.provide('windows:devtools:markdown-stress', () => setupDevtoolsWindow())
const onboardingWindow = injeca.provide('windows:onboarding', () => setupOnboardingWindowManager())
const noticeWindow = injeca.provide('windows:notice', {
dependsOn: { i18n, serverChannel },
build: ({ dependsOn }) => setupNoticeWindowManager(dependsOn),
@@ -152,6 +155,12 @@ app.whenReady().then(async () => {
callback: noop,
})
// Show onboarding window if not completed
injeca.invoke({
dependsOn: { onboardingWindow },
callback: ({ onboardingWindow }) => { onboardingWindow.showIfNeeded() },
})
injeca.start().catch(err => console.error(err))
// Lifecycle
@@ -0,0 +1,117 @@
import { join, resolve } from 'node:path'
import { is } from '@electron-toolkit/utils'
import { defineInvokeHandler } from '@moeru/eventa'
import { createContext } from '@moeru/eventa/adapters/electron/main'
import { BrowserWindow, ipcMain, shell } from 'electron'
import icon from '../../../../resources/icon.png?asset'
import { electronOnboardingClose, electronOnboardingCompleted, electronOnboardingSkipped, electronOpenOnboarding } from '../../../shared/eventa'
import { baseUrl, getElectronMainDirname, load, withHashRoute } from '../../libs/electron/location'
export interface OnboardingWindowManager {
getWindow: () => BrowserWindow | null
showIfNeeded: () => Promise<boolean>
close: () => void
markCompleted: () => void
markSkipped: () => void
}
export function setupOnboardingWindowManager(): OnboardingWindowManager {
let window: BrowserWindow | undefined
const close = () => {
if (window && !window.isDestroyed()) {
window.close()
}
}
const markCompleted = () => {
close()
}
const markSkipped = () => {
close()
}
const createWindow = async () => {
if (window && !window.isDestroyed()) {
return window
}
const newWindow = new BrowserWindow({
title: 'Welcome to AIRI',
width: 500,
height: 700,
minWidth: 400,
minHeight: 500,
show: false,
icon,
resizable: true,
frame: false,
titleBarStyle: 'hidden',
transparent: false,
backgroundColor: '#0f0f0f',
webPreferences: {
preload: join(getElectronMainDirname(), '../preload/index.mjs'),
sandbox: false,
},
})
// NOTICE: in development mode, open devtools by default
if (is.dev) {
try {
newWindow.webContents.openDevTools({ mode: 'detach' })
}
catch (err) {
console.error('failed to open devtools:', err)
}
}
newWindow.on('ready-to-show', () => newWindow.show())
newWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
newWindow.on('closed', () => {
window = undefined
})
await load(newWindow, withHashRoute(baseUrl(resolve(getElectronMainDirname(), '..', 'renderer')), '/onboarding'))
// Setup IPC handlers for this window
const { context } = createContext(ipcMain, newWindow)
defineInvokeHandler(context, electronOnboardingClose, () => close())
defineInvokeHandler(context, electronOnboardingCompleted, () => markCompleted())
defineInvokeHandler(context, electronOnboardingSkipped, () => markSkipped())
defineInvokeHandler(context, electronOpenOnboarding, async () => {
await showIfNeeded()
return true
})
window = newWindow
return newWindow
}
async function showIfNeeded() {
if (window && !window.isDestroyed()) {
window.show()
window.focus()
return true
}
await createWindow()
return true
}
return {
getWindow: () => window ?? null,
showIfNeeded,
close,
markCompleted,
markSkipped,
}
}
@@ -1,31 +1,69 @@
<script setup lang="ts">
import { useElectronEventaInvoke } from '@proj-airi/electron-vueuse'
import { OnboardingScreen } from '@proj-airi/stage-ui/components'
import { useOnboardingStore } from '@proj-airi/stage-ui/stores/onboarding'
import { useTheme } from '@proj-airi/ui'
import { computed } from 'vue'
import {
electronOnboardingClose,
electronOnboardingCompleted,
electronOnboardingSkipped,
} from '../../shared/eventa'
// const window = useTauriWindow()
const onboardingStore = useOnboardingStore()
const { isDark } = useTheme()
function handleSkipped() {
// window.closeWindow()
const bgClass = computed(() => isDark.value ? 'bg-[#0f0f0f]' : 'bg-white')
const closeWindow = useElectronEventaInvoke(electronOnboardingClose)
const markCompleted = useElectronEventaInvoke(electronOnboardingCompleted)
const markSkipped = useElectronEventaInvoke(electronOnboardingSkipped)
async function handleSkipped() {
onboardingStore.markSetupSkipped()
await markSkipped()
await closeWindow()
}
function handleConfigured() {
async function handleConfigured() {
onboardingStore.markSetupCompleted()
// window.closeWindow()
await markCompleted()
await closeWindow()
}
</script>
<template>
<div h-full w-full flex flex-col overflow-x-hidden overflow-y-hidden overscroll-none>
<div bg="white dark:#181818" w="100dvw" min-h="12" w-full flex-shrink-0 select-none data-tauri-drag-region />
<div w-full flex-1 overflow-y-scroll px-3>
<div h-full py-3>
<div class="onboarding-root" h-full w-full flex flex-col overflow-x-hidden overflow-y-auto overscroll-none :class="bgClass">
<div :class="bgClass" w="100dvw" min-h="12" w-full flex-shrink-0 select-none data-tauri-drag-region />
<div class="onboarding-scroll" w-full flex-1 px-3>
<div class="onboarding-content" h-full>
<OnboardingScreen @skipped="handleSkipped" @configured="handleConfigured" />
</div>
</div>
</div>
</template>
<style scoped>
.onboarding-root {
scrollbar-width: none;
}
.onboarding-root::-webkit-scrollbar {
display: none;
}
.onboarding-content {
padding: 8px 0 20px 0;
}
.onboarding-scroll {
padding-top: 8px;
padding-bottom: 20px;
overflow-y: auto;
}
</style>
<route lang="yaml">
meta:
layout: plain
@@ -206,6 +206,12 @@ export const widgetsRemoveEvent = defineEventa<{ id: string }>('eventa:event:ele
export const widgetsClearEvent = defineEventa('eventa:event:electron:windows:widgets:clear')
export const widgetsUpdateEvent = defineEventa<{ id: string, componentProps?: Record<string, any> }>('eventa:event:electron:windows:widgets:update')
// Onboarding window events
export const electronOnboardingClose = defineInvokeEventa('eventa:invoke:electron:windows:onboarding:close')
export const electronOnboardingCompleted = defineInvokeEventa('eventa:invoke:electron:windows:onboarding:completed')
export const electronOnboardingSkipped = defineInvokeEventa('eventa:invoke:electron:windows:onboarding:skipped')
export const electronOpenOnboarding = defineInvokeEventa<boolean>('eventa:invoke:electron:windows:onboarding:open')
export const i18nSetLocale = defineInvokeEventa<void, Locale>('eventa:invoke:electron:i18n:set-locale')
export const i18nGetLocale = defineInvokeEventa<Locale>('eventa:invoke:electron:i18n:get-locale')