chore(tamagotchi): move settings a single window (#14)
* move settings to a new window * use custom menu * fix routing
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
import { join, resolve } from 'node:path'
|
||||
import { Download } from '@proj-airi/unplugin-download'
|
||||
import { DownloadLive2DSDK } from '@proj-airi/unplugin-live2d-sdk'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||
import unocss from 'unocss/vite'
|
||||
|
||||
import rendererConfig from './renderer.vite.config'
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
@@ -12,25 +9,5 @@ export default defineConfig({
|
||||
preload: {
|
||||
plugins: [externalizeDepsPlugin()],
|
||||
},
|
||||
renderer: {
|
||||
optimizeDeps: {
|
||||
exclude: [
|
||||
'@proj-airi/stage-ui/*',
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@renderer': resolve(join('src', 'renderer', 'src')),
|
||||
'@proj-airi/stage-ui': resolve(join(import.meta.dirname, '..', 'stage-ui', 'dist')),
|
||||
'@proj-airi/stage-ui/stores': resolve(join(import.meta.dirname, '..', 'stage-ui', 'dist', 'stores')),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
unocss(),
|
||||
DownloadLive2DSDK(),
|
||||
Download('https://dist.ayaka.moe/live2d-models/hiyori_free_zh.zip', 'hiyori_free_zh.zip', 'assets/live2d/models'),
|
||||
Download('https://dist.ayaka.moe/live2d-models/hiyori_pro_zh.zip', 'hiyori_pro_zh.zip', 'assets/live2d/models'),
|
||||
],
|
||||
},
|
||||
renderer: rendererConfig,
|
||||
})
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { join, resolve } from 'node:path'
|
||||
import { Download } from '@proj-airi/unplugin-download'
|
||||
import { DownloadLive2DSDK } from '@proj-airi/unplugin-live2d-sdk'
|
||||
import Vue from '@vitejs/plugin-vue'
|
||||
import UnoCss from 'unocss/vite'
|
||||
import VueRouter from 'unplugin-vue-router/vite'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
optimizeDeps: {
|
||||
exclude: [
|
||||
'@proj-airi/stage-ui/*',
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@renderer': resolve(join('src', 'renderer', 'src')),
|
||||
'@proj-airi/stage-ui': resolve(join(import.meta.dirname, '..', 'stage-ui', 'dist')),
|
||||
'@proj-airi/stage-ui/stores': resolve(join(import.meta.dirname, '..', 'stage-ui', 'dist', 'stores')),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
Vue(),
|
||||
UnoCss(),
|
||||
VueRouter({
|
||||
dts: resolve(import.meta.dirname, 'src/typed-router.d.ts'),
|
||||
routesFolder: 'src/renderer/src/pages',
|
||||
}),
|
||||
DownloadLive2DSDK(),
|
||||
Download('https://dist.ayaka.moe/live2d-models/hiyori_free_zh.zip', 'hiyori_free_zh.zip', 'assets/live2d/models'),
|
||||
Download('https://dist.ayaka.moe/live2d-models/hiyori_pro_zh.zip', 'hiyori_pro_zh.zip', 'assets/live2d/models'),
|
||||
],
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
import { join } from 'node:path'
|
||||
import { env, platform } from 'node:process'
|
||||
import { electronApp, is, optimizer } from '@electron-toolkit/utils'
|
||||
import { app, BrowserWindow, ipcMain, shell } from 'electron'
|
||||
import { app, BrowserWindow, dialog, ipcMain, Menu, shell } from 'electron'
|
||||
import icon from '../../build/icon.png?asset'
|
||||
|
||||
function createWindow(): void {
|
||||
@@ -50,10 +50,78 @@ function createWindow(): void {
|
||||
})
|
||||
}
|
||||
|
||||
let settingsWindow: BrowserWindow | null = null
|
||||
|
||||
function createSettingsWindow(): void {
|
||||
if (settingsWindow) {
|
||||
settingsWindow.show()
|
||||
return
|
||||
}
|
||||
|
||||
settingsWindow = new BrowserWindow({
|
||||
width: 300,
|
||||
height: 400,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
preload: join(import.meta.dirname, '..', 'preload', 'index.js'),
|
||||
sandbox: false,
|
||||
},
|
||||
})
|
||||
|
||||
settingsWindow.on('ready-to-show', () => {
|
||||
settingsWindow?.show()
|
||||
})
|
||||
|
||||
settingsWindow.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
settingsWindow.on('close', () => {
|
||||
settingsWindow = null
|
||||
})
|
||||
|
||||
settingsWindow.show()
|
||||
|
||||
if (is.dev && env.ELECTRON_RENDERER_URL) {
|
||||
settingsWindow.loadURL(join(env.ELECTRON_RENDERER_URL, '#/settings'))
|
||||
}
|
||||
else {
|
||||
settingsWindow.loadFile(join(import.meta.dirname, '..', '..', 'out', 'renderer', 'index.html'), {
|
||||
hash: '/settings',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.whenReady().then(() => {
|
||||
// Menu
|
||||
const menu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'airi',
|
||||
role: 'appMenu',
|
||||
submenu: [
|
||||
{
|
||||
role: 'about',
|
||||
},
|
||||
{
|
||||
role: 'toggleDevTools',
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
click: () => createSettingsWindow(),
|
||||
},
|
||||
{
|
||||
label: 'Quit',
|
||||
click: () => app.quit(),
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
Menu.setApplicationMenu(menu)
|
||||
|
||||
// Set app user model id for windows
|
||||
electronApp.setAppUserModelId('com.github.moeru-ai.airi-tamagotchi')
|
||||
|
||||
@@ -65,7 +133,21 @@ app.whenReady().then(() => {
|
||||
})
|
||||
|
||||
// IPC test
|
||||
ipcMain.on('quit', () => app.quit())
|
||||
// TODO: i18n
|
||||
ipcMain.on('quit', () => {
|
||||
dialog.showMessageBox({
|
||||
type: 'info',
|
||||
title: 'Quit',
|
||||
message: 'Are you sure you want to quit?',
|
||||
buttons: ['Quit', 'Cancel'],
|
||||
}).then((result) => {
|
||||
if (result.response === 0) {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
ipcMain.on('open-settings', () => createSettingsWindow())
|
||||
|
||||
createWindow()
|
||||
|
||||
|
||||
@@ -1,49 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { WidgetStage } from '@proj-airi/stage-ui/components'
|
||||
import { ref } from 'vue'
|
||||
import InteractiveArea from './components/InteractiveArea.vue'
|
||||
|
||||
const dragDelay = ref(0)
|
||||
const isDragging = ref(false)
|
||||
|
||||
function handleMouseDown() {
|
||||
dragDelay.value = window.setTimeout(() => {
|
||||
isDragging.value = true
|
||||
}, 500)
|
||||
}
|
||||
|
||||
function handleMouseUp() {
|
||||
clearTimeout(dragDelay.value)
|
||||
isDragging.value = false
|
||||
}
|
||||
|
||||
function handleMouseLeave() {
|
||||
isDragging.value = false
|
||||
}
|
||||
|
||||
function handleMouseMove(event: MouseEvent) {
|
||||
if (isDragging.value) {
|
||||
window.electron.ipcRenderer.send('move-window', event.movementX, event.movementY)
|
||||
}
|
||||
}
|
||||
import { RouterView } from 'vue-router'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div relative max-h="[100vh]" max-w="[100vw]" p="2" flex="~ col" z-2 h-full overflow-hidden @mousedown="handleMouseDown" @mouseup="handleMouseUp" @mousemove="handleMouseMove" @mouseleave="handleMouseLeave">
|
||||
<div relative h-full w-full items-end gap-2 class="view">
|
||||
<WidgetStage h-full w-full flex-1 mb="<md:18" />
|
||||
<InteractiveArea class="interaction-area block" pointer-events-none absolute bottom-0 w-full opacity-0 transition="opacity duration-250" />
|
||||
</div>
|
||||
</div>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.view {
|
||||
&:hover {
|
||||
.interaction-area {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,11 +7,9 @@ import { useChatStore, useSettings } from '@proj-airi/stage-ui/stores'
|
||||
|
||||
// import { useDevicesList } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { DrawerContent, DrawerPortal, DrawerRoot, DrawerTrigger } from 'vaul-vue'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import TamagotchiChatHistory from './ChatHistory.vue'
|
||||
import TamagotchiSettings from './Settings.vue'
|
||||
|
||||
const messageInput = ref('')
|
||||
const listening = ref(false)
|
||||
@@ -72,6 +70,10 @@ function handleTranscription(_buffer: Float32Array<ArrayBufferLike>) {
|
||||
// selectedAudioDevice.value = found
|
||||
// }
|
||||
|
||||
function openSettings() {
|
||||
window.electron.ipcRenderer.send('open-settings')
|
||||
}
|
||||
|
||||
watch(isAudioInputOn, async (value) => {
|
||||
if (value === 'false') {
|
||||
destroy()
|
||||
@@ -103,26 +105,16 @@ onMounted(() => {
|
||||
@submit="handleSend"
|
||||
/>
|
||||
</div>
|
||||
<DrawerRoot should-scale-background>
|
||||
<DrawerTrigger
|
||||
class="px-4 py-2.5"
|
||||
border="solid 2 pink-100 "
|
||||
text="lg pink-400 hover:pink-600 placeholder:pink-400 placeholder:hover:pink-600"
|
||||
bg="pink-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]" rounded-r-xl
|
||||
>
|
||||
<div i-solar:settings-bold-duotone />
|
||||
</DrawerTrigger>
|
||||
<DrawerPortal>
|
||||
<DrawerContent
|
||||
max-h="[90%]"
|
||||
fixed bottom-0 left-0 right-0 z-50 mt-24 h-full flex flex-col rounded-t-lg bg="[#fffbff] dark:[#1f1a1d]"
|
||||
>
|
||||
<div class="flex flex-1 flex-col rounded-t-lg p-5" bg="[#fffbff] dark:[#1f1a1d]" gap-2>
|
||||
<TamagotchiSettings />
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</DrawerPortal>
|
||||
</DrawerRoot>
|
||||
<div
|
||||
class="px-4 py-2.5"
|
||||
border="solid 2 pink-100 "
|
||||
text="lg pink-400 hover:pink-600 placeholder:pink-400 placeholder:hover:pink-600"
|
||||
bg="pink-50 dark:[#3c2632]" max-h="[10lh]" min-h="[1lh]"
|
||||
flex items-center justify-center rounded-r-xl
|
||||
@click="openSettings"
|
||||
>
|
||||
<div i-solar:settings-bold-duotone />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -4,8 +4,11 @@ import { MotionPlugin } from '@vueuse/motion'
|
||||
import { createPinia } from 'pinia'
|
||||
import { createApp } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import App from './App.vue'
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
|
||||
import { routes } from 'vue-router/auto-routes'
|
||||
|
||||
import App from './App.vue'
|
||||
import '@unocss/reset/tailwind.css'
|
||||
import 'uno.css'
|
||||
import './main.css'
|
||||
@@ -22,9 +25,15 @@ const i18n = createI18n({
|
||||
},
|
||||
})
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
createApp(App)
|
||||
.use(MotionPlugin)
|
||||
.use(autoAnimatePlugin)
|
||||
.use(router)
|
||||
.use(pinia)
|
||||
.use(i18n)
|
||||
.use(Tres)
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { WidgetStage } from '@proj-airi/stage-ui/components'
|
||||
import { ref } from 'vue'
|
||||
import InteractiveArea from '../components/InteractiveArea.vue'
|
||||
|
||||
const dragDelay = ref(0)
|
||||
const isDragging = ref(false)
|
||||
|
||||
function handleMouseDown() {
|
||||
dragDelay.value = window.setTimeout(() => {
|
||||
isDragging.value = true
|
||||
}, 500)
|
||||
}
|
||||
|
||||
function handleMouseUp() {
|
||||
clearTimeout(dragDelay.value)
|
||||
isDragging.value = false
|
||||
}
|
||||
|
||||
function handleMouseLeave() {
|
||||
isDragging.value = false
|
||||
}
|
||||
|
||||
function handleMouseMove(event: MouseEvent) {
|
||||
if (isDragging.value) {
|
||||
window.electron.ipcRenderer.send('move-window', event.movementX, event.movementY)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div relative max-h="[100vh]" max-w="[100vw]" p="2" flex="~ col" z-2 h-full overflow-hidden @mousedown="handleMouseDown" @mouseup="handleMouseUp" @mousemove="handleMouseMove" @mouseleave="handleMouseLeave">
|
||||
<div relative h-full w-full items-end gap-2 class="view">
|
||||
<WidgetStage h-full w-full flex-1 mb="<md:18" />
|
||||
<InteractiveArea class="interaction-area block" pointer-events-none absolute bottom-0 w-full opacity-0 transition="opacity duration-250" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.view {
|
||||
&:hover {
|
||||
.interaction-area {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+23
-1
@@ -65,10 +65,14 @@ onMounted(async () => {
|
||||
|
||||
supportedModels.value = await models(openAiApiBaseURL.value, openAiApiKey.value)
|
||||
})
|
||||
|
||||
function handleQuit() {
|
||||
window.electron.ipcRenderer.send('quit')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div m-4>
|
||||
<h2 text="slate-800/80" font-bold>
|
||||
Settings
|
||||
</h2>
|
||||
@@ -197,5 +201,23 @@ onMounted(async () => {
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<h2 text="slate-800/80" font-bold>
|
||||
Other
|
||||
</h2>
|
||||
<div pb-2>
|
||||
<div
|
||||
grid="~ cols-[140px_1fr]" my-2 items-center gap-1.5 rounded-lg
|
||||
bg="[#fff6fc]" p-2 text="pink-400" @click="handleQuit"
|
||||
>
|
||||
<div text="xs pink-500">
|
||||
<span>
|
||||
Quit
|
||||
</span>
|
||||
</div>
|
||||
<div text="sm pink-500" text-right>
|
||||
<div i-solar:exit-bold-duotone ml-auto />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-router. ‼️ DO NOT MODIFY THIS FILE ‼️
|
||||
// It's recommended to commit this file.
|
||||
// Make sure to add this file to your tsconfig.json file as an "includes" or "files" entry.
|
||||
|
||||
declare module 'vue-router/auto-routes' {
|
||||
import type {
|
||||
RouteRecordInfo,
|
||||
ParamValue,
|
||||
ParamValueOneOrMore,
|
||||
ParamValueZeroOrMore,
|
||||
ParamValueZeroOrOne,
|
||||
} from 'vue-router'
|
||||
|
||||
/**
|
||||
* Route name map generated by unplugin-vue-router
|
||||
*/
|
||||
export interface RouteNamedMap {
|
||||
'/': RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>,
|
||||
'/settings': RouteRecordInfo<'/settings', '/settings', Record<never, never>, Record<never, never>>,
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@
|
||||
"types": ["electron-vite/node"]
|
||||
},
|
||||
"include": [
|
||||
"renderer.vite.config.*",
|
||||
"electron.vite.config.*",
|
||||
"src/main/**/*",
|
||||
"src/preload/**/*",
|
||||
"../../vite-plugins/**/*"
|
||||
"src/preload/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user