feat(stage-tamagotchi): open settings page & open devtools in Developer section
This commit is contained in:
@@ -19,7 +19,7 @@ import { emitAppBeforeQuit, emitAppReady, emitAppWindowAllClosed, onAppBeforeQui
|
||||
import { setElectronMainDirname } from './libs/electron/location'
|
||||
import { setupInlayWindow } from './windows/inlay'
|
||||
import { setupMainWindow } from './windows/main'
|
||||
import { setupSettingsWindow } from './windows/settings'
|
||||
import { setupSettingsWindowReusableFunc } from './windows/settings'
|
||||
import { toggleWindowShow } from './windows/shared/window'
|
||||
|
||||
setElectronMainDirname(__dirname)
|
||||
@@ -32,7 +32,10 @@ const log = useLogg('main').useGlobalConfig()
|
||||
app.dock?.setIcon(icon)
|
||||
electronApp.setAppUserModelId('ai.moeru.airi')
|
||||
|
||||
function setupTray(params: { mainWindow: BrowserWindow }): void {
|
||||
function setupTray(params: {
|
||||
mainWindow: BrowserWindow
|
||||
settingsWindow: () => Promise<BrowserWindow>
|
||||
}): void {
|
||||
once(() => {
|
||||
const appTray = new Tray(nativeImage.createFromPath(macOSTrayIcon).resize({ width: 16 }))
|
||||
onAppBeforeQuit(() => appTray.destroy())
|
||||
@@ -41,7 +44,7 @@ function setupTray(params: { mainWindow: BrowserWindow }): void {
|
||||
{ label: 'Show', click: () => toggleWindowShow(params.mainWindow) },
|
||||
{ label: 'Inlay', click: () => setupInlayWindow() },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Settings', click: () => setupSettingsWindow() },
|
||||
{ label: 'Settings', click: () => params.settingsWindow().then(window => toggleWindowShow(window)) },
|
||||
{ type: 'separator' },
|
||||
{ label: 'Quit', click: () => app.quit() },
|
||||
])
|
||||
@@ -92,9 +95,10 @@ app.whenReady().then(async () => {
|
||||
await setupProjectAIRIServerRuntime()
|
||||
|
||||
injecta.setLogger(createLoggLogger())
|
||||
injecta.provide('mainWindow', async () => await setupMainWindow())
|
||||
injecta.provide<{ mainWindow: BrowserWindow }>('tray', { dependsOn: { mainWindow: 'mainWindow' }, build: async ({ dependsOn }) => setupTray({ mainWindow: dependsOn.mainWindow }) })
|
||||
injecta.invoke({ dependsOn: { mainWindow: 'mainWindow', tray: 'tray' }, callback: noop })
|
||||
injecta.provide('windows:settings', () => setupSettingsWindowReusableFunc())
|
||||
injecta.provide<{ settingsWindow: () => Promise<BrowserWindow> }>('windows:main', { dependsOn: { settingsWindow: 'windows:settings' }, build: async ({ dependsOn }) => setupMainWindow(dependsOn) })
|
||||
injecta.provide<{ mainWindow: BrowserWindow, settingsWindow: () => Promise<BrowserWindow> }>('tray', { dependsOn: { mainWindow: 'windows:main', settingsWindow: 'windows:settings' }, build: async ({ dependsOn }) => setupTray(dependsOn) })
|
||||
injecta.invoke({ dependsOn: { mainWindow: 'windows:main', tray: 'tray' }, callback: noop })
|
||||
injecta.start()
|
||||
|
||||
// Lifecycle
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './reusable'
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { BrowserWindow } from 'electron'
|
||||
|
||||
export function createReusableWindow(setupFn: () => BrowserWindow | Promise<BrowserWindow>): { getWindow: () => Promise<BrowserWindow> } {
|
||||
let window: BrowserWindow | undefined
|
||||
|
||||
return {
|
||||
getWindow: async () => {
|
||||
if (!window) {
|
||||
window = await setupFn()
|
||||
return window
|
||||
}
|
||||
if (window.isDestroyed()) {
|
||||
window = await setupFn()
|
||||
return window
|
||||
}
|
||||
|
||||
return window
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,14 @@
|
||||
import { dirname, join, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { join, resolve } from 'node:path'
|
||||
|
||||
import { BrowserWindow, shell } from 'electron'
|
||||
import { isMacOS } from 'std-env'
|
||||
|
||||
import icon from '../../../../resources/icon.png?asset'
|
||||
|
||||
import { baseUrl, load, withHashRoute } from '../../libs/electron/location'
|
||||
import { baseUrl, getElectronMainDirname, load, withHashRoute } from '../../libs/electron/location'
|
||||
import { currentDisplayBounds, mapForBreakpoints, resolutionBreakpoints, widthFrom } from '../shared/display'
|
||||
import { spotlightLikeWindowConfig } from '../shared/window'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
export async function setupInlayWindow() {
|
||||
const window = new BrowserWindow({
|
||||
title: 'Inlay',
|
||||
@@ -20,7 +17,7 @@ export async function setupInlayWindow() {
|
||||
show: false,
|
||||
icon,
|
||||
webPreferences: {
|
||||
preload: join(dirname(fileURLToPath(import.meta.url)), '../preload/index.mjs'),
|
||||
preload: join(__dirname, '../preload/index.mjs'),
|
||||
sandbox: false,
|
||||
},
|
||||
...spotlightLikeWindowConfig(),
|
||||
@@ -63,7 +60,7 @@ export async function setupInlayWindow() {
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
await load(window, withHashRoute(baseUrl(resolve(__dirname, '..', '..', '..', 'renderer')), '/inlay'))
|
||||
await load(window, withHashRoute(baseUrl(resolve(getElectronMainDirname(), '..', 'renderer')), '/inlay'))
|
||||
|
||||
return window
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import type { BrowserWindow } from 'electron'
|
||||
|
||||
import { defineInvokeHandler } from '@unbird/eventa'
|
||||
import { createContext } from '@unbird/eventa/adapters/electron/main'
|
||||
import { ipcMain } from 'electron'
|
||||
|
||||
import { electronOpenMainDevtools, electronOpenSettings } from '../../../../shared/eventa'
|
||||
import { createFadeOnHoverService } from '../../../services/fade-on-hover'
|
||||
import { toggleWindowShow } from '../../shared'
|
||||
|
||||
export function setupAppInvokeHandlers(window: BrowserWindow) {
|
||||
const eventaContext = createContext(ipcMain, window)
|
||||
const { context } = eventaContext
|
||||
export function setupMainWindowElectronInvokes(params: { window: BrowserWindow, settingsWindow: () => Promise<BrowserWindow> }) {
|
||||
const { context } = createContext(ipcMain, params.window)
|
||||
|
||||
createFadeOnHoverService(context)
|
||||
defineInvokeHandler(context, electronOpenMainDevtools, () => params.window.webContents.openDevTools({ mode: 'detach' }))
|
||||
defineInvokeHandler(context, electronOpenSettings, async () => toggleWindowShow(await params.settingsWindow()))
|
||||
}
|
||||
|
||||
@@ -5,27 +5,32 @@ import { BrowserWindow, shell } from 'electron'
|
||||
import icon from '../../../../resources/icon.png?asset'
|
||||
|
||||
import { baseUrl, getElectronMainDirname, load, withHashRoute } from '../../libs/electron/location'
|
||||
import { createReusableWindow } from '../../libs/electron/window-manager'
|
||||
import { setupSettingsWindowInvokes } from './rpc/index.electron'
|
||||
|
||||
export function setupSettingsWindowReusableFunc() {
|
||||
const window = new BrowserWindow({
|
||||
title: 'Settings',
|
||||
width: 600.0,
|
||||
height: 800.0,
|
||||
show: false,
|
||||
icon,
|
||||
webPreferences: {
|
||||
preload: join(dirname(fileURLToPath(import.meta.url)), '../preload/index.mjs'),
|
||||
sandbox: false,
|
||||
},
|
||||
})
|
||||
return createReusableWindow(async () => {
|
||||
const window = new BrowserWindow({
|
||||
title: 'Settings',
|
||||
width: 600.0,
|
||||
height: 800.0,
|
||||
show: false,
|
||||
icon,
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.mjs'),
|
||||
sandbox: false,
|
||||
},
|
||||
})
|
||||
|
||||
window.on('ready-to-show', () => window.show())
|
||||
window.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
window.on('ready-to-show', () => window.show())
|
||||
window.webContents.setWindowOpenHandler((details) => {
|
||||
shell.openExternal(details.url)
|
||||
return { action: 'deny' }
|
||||
})
|
||||
|
||||
await load(window, withHashRoute(baseUrl(resolve(getElectronMainDirname(), '..', 'renderer')), '/settings'))
|
||||
await load(window, withHashRoute(baseUrl(resolve(getElectronMainDirname(), '..', 'renderer')), '/settings'))
|
||||
await setupSettingsWindowInvokes({ settingsWindow: window })
|
||||
|
||||
return window
|
||||
return window
|
||||
}).getWindow
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { BrowserWindow } from 'electron'
|
||||
|
||||
import { defineInvokeHandler } from '@unbird/eventa'
|
||||
import { createContext } from '@unbird/eventa/adapters/electron/main'
|
||||
import { ipcMain } from 'electron'
|
||||
|
||||
import { electronOpenSettingsDevtools } from '../../../../shared/eventa'
|
||||
|
||||
export async function setupSettingsWindowInvokes(params: { settingsWindow: BrowserWindow }) {
|
||||
const { context } = createContext(ipcMain, params.settingsWindow)
|
||||
|
||||
defineInvokeHandler(context, electronOpenSettingsDevtools, async () => params.settingsWindow.webContents.openDevTools({ mode: 'detach' }))
|
||||
}
|
||||
@@ -1,21 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { HearingConfigDialog } from '@proj-airi/stage-ui/components'
|
||||
import { useSettingsAudioDevice } from '@proj-airi/stage-ui/stores/settings'
|
||||
import { defineInvoke } from '@unbird/eventa'
|
||||
import { createContext } from '@unbird/eventa/adapters/electron/renderer'
|
||||
import { useDark, useToggle } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
import ControlButton from './ControlButton.vue'
|
||||
|
||||
import { electronOpenSettings } from '../../../../shared/eventa'
|
||||
|
||||
const isDark = useDark({ disableTransition: false })
|
||||
const toggleDark = useToggle(isDark)
|
||||
|
||||
const settingsAudioDeviceStore = useSettingsAudioDevice()
|
||||
const { enabled } = storeToRefs(settingsAudioDeviceStore)
|
||||
|
||||
const { context } = createContext(window.electron.ipcRenderer)
|
||||
const openSettings = defineInvoke(context, electronOpenSettings)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div fixed bottom-2 right-2>
|
||||
<div flex flex-col gap-1>
|
||||
<ControlButton @click="() => openSettings()">
|
||||
<div i-solar:settings-minimalistic-outline size-5 text="neutral-800 dark:neutral-300" />
|
||||
</ControlButton>
|
||||
<HearingConfigDialog>
|
||||
<ControlButton>
|
||||
<Transition name="fade" mode="out-in">
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { CheckBar, IconItem } from '@proj-airi/stage-ui/components'
|
||||
import { ButtonBar, CheckBar, IconItem } from '@proj-airi/stage-ui/components'
|
||||
import { useSettings } from '@proj-airi/stage-ui/stores/settings'
|
||||
import { defineInvoke } from '@unbird/eventa'
|
||||
import { createContext } from '@unbird/eventa/adapters/electron/renderer'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { electronOpenMainDevtools } from '../../../../shared/eventa'
|
||||
|
||||
const { t } = useI18n()
|
||||
const settings = useSettings()
|
||||
|
||||
@@ -15,9 +19,27 @@ const menu = computed(() => [
|
||||
to: '/devtools/use-magic-keys',
|
||||
},
|
||||
])
|
||||
|
||||
const { context } = createContext(window.electron.ipcRenderer)
|
||||
const openDevTools = defineInvoke(context, electronOpenMainDevtools)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ButtonBar
|
||||
v-model="settings.disableTransitions"
|
||||
v-motion
|
||||
mb-2
|
||||
icon="i-solar:settings-minimalistic-outline"
|
||||
text="settings.pages.page.developers.open-devtools.title"
|
||||
:initial="{ opacity: 0, y: 10 }"
|
||||
:enter="{ opacity: 1, y: 0 }"
|
||||
:duration="250 + (19 * 10)"
|
||||
:delay="1 * 50"
|
||||
transition="all ease-in-out duration-250"
|
||||
@click="() => openDevTools()"
|
||||
>
|
||||
{{ t('settings.pages.page.developers.open-devtools.button') }}
|
||||
</ButtonBar>
|
||||
<CheckBar
|
||||
v-model="settings.disableTransitions"
|
||||
v-motion
|
||||
@@ -28,7 +50,7 @@ const menu = computed(() => [
|
||||
:initial="{ opacity: 0, y: 10 }"
|
||||
:enter="{ opacity: 1, y: 0 }"
|
||||
:duration="250 + (19 * 10)"
|
||||
:delay="1 * 50"
|
||||
:delay="2 * 50"
|
||||
transition="all ease-in-out duration-250"
|
||||
/>
|
||||
<CheckBar
|
||||
@@ -42,7 +64,7 @@ const menu = computed(() => [
|
||||
:initial="{ opacity: 0, y: 10 }"
|
||||
:enter="{ opacity: 1, y: 0 }"
|
||||
:duration="250 + (20 * 10)"
|
||||
:delay="2 * 50"
|
||||
:delay="3 * 50"
|
||||
transition="all ease-in-out duration-250"
|
||||
/>
|
||||
|
||||
@@ -55,7 +77,7 @@ const menu = computed(() => [
|
||||
:enter="{ opacity: 1, y: 0 }"
|
||||
:duration="250"
|
||||
:style="{
|
||||
transitionDelay: `${index * 50}ms`, // delay between each item, unocss doesn't support dynamic generation of classes now
|
||||
transitionDelay: `${(4 + index) * 50}ms`, // delay between each item, unocss doesn't support dynamic generation of classes now
|
||||
}"
|
||||
:title="item.title"
|
||||
:description="item.description"
|
||||
|
||||
@@ -15,4 +15,6 @@ interface Point {
|
||||
|
||||
export const electronCursorPoint = defineEventa<Point>('electron:eventa:event:cursor-point')
|
||||
export const electronStartTrackingCursorPoint = defineInvokeEventa('electron:eventa:invoke:start-tracking-cursor-point')
|
||||
export const electronOpenSettings = defineInvokeEventa<void, void>('electron:eventa:invoke:open-settings')
|
||||
export const electronOpenMainDevtools = defineInvokeEventa<void, void>('electron:eventa:invoke:windows:main:devtools:open')
|
||||
export const electronOpenSettings = defineInvokeEventa<void, void>('electron:eventa:invoke:windows:settings:open')
|
||||
export const electronOpenSettingsDevtools = defineInvokeEventa<void, void>('electron:eventa:invoke:windows:settings:devtools:open')
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
common:
|
||||
save: Save
|
||||
|
||||
animations:
|
||||
stage-transitions:
|
||||
title: Disable Stage Transitions
|
||||
@@ -186,19 +185,27 @@ pages:
|
||||
title: Parameters
|
||||
parameters:
|
||||
adaptive_threshold:
|
||||
description: Whether to apply adaptive thresholds based on signal variance over time.
|
||||
description: >-
|
||||
Whether to apply adaptive thresholds based on signal variance
|
||||
over time.
|
||||
label: Adaptive threshold
|
||||
buffer_duration:
|
||||
description: Duration of the internal analysis buffer.
|
||||
label: Buffer duration
|
||||
envelope_filter_frequency:
|
||||
description: Frequency for the envelope filter applied to smooth energy changes.
|
||||
description: >-
|
||||
Frequency for the envelope filter applied to smooth energy
|
||||
changes.
|
||||
label: Envelope filter frequency
|
||||
highpass_filter_frequency:
|
||||
description: Frequency for the highpass filter applied to reduce low frequencies like sub-bass noises.
|
||||
description: >-
|
||||
Frequency for the highpass filter applied to reduce low
|
||||
frequencies like sub-bass noises.
|
||||
label: Highpass filter frequency
|
||||
lowpass_filter_frequency:
|
||||
description: Frequency for the lowpass filter applied to reduce high frequencies like vocals.
|
||||
description: >-
|
||||
Frequency for the lowpass filter applied to reduce high
|
||||
frequencies like vocals.
|
||||
label: Lowpass filter frequency
|
||||
min_beat_interval:
|
||||
description: Maximum BPM or minimum interval between detected beats.
|
||||
@@ -671,6 +678,11 @@ pages:
|
||||
title: Chinese Traditional Colors
|
||||
title: Color Scheme Presets
|
||||
title: System
|
||||
page:
|
||||
developers:
|
||||
open-devtools:
|
||||
title: Open Developer Tools
|
||||
button: Open
|
||||
sections:
|
||||
section:
|
||||
general:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
common:
|
||||
save: 保存
|
||||
|
||||
animations:
|
||||
stage-transitions:
|
||||
title: 是否开启舞台动画
|
||||
@@ -601,6 +600,11 @@ pages:
|
||||
title: 中国传统颜色
|
||||
title: 预设
|
||||
title: 外观
|
||||
page:
|
||||
developers:
|
||||
open-devtools:
|
||||
button: 打开
|
||||
title: 打开开发者工具
|
||||
sections:
|
||||
section:
|
||||
general:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
common:
|
||||
save: 儲存
|
||||
|
||||
animations:
|
||||
stage-transitions:
|
||||
title: 是否開啟舞台動畫
|
||||
@@ -507,6 +506,10 @@ pages:
|
||||
title: 配色
|
||||
developer:
|
||||
title: 開發者
|
||||
sections:
|
||||
section:
|
||||
use-magic-keys:
|
||||
description: 測試快捷鍵
|
||||
theme-presets:
|
||||
presets:
|
||||
- colors:
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
text: string
|
||||
description?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label
|
||||
class="w-full flex cursor-pointer items-center justify-between rounded-lg px-4 py-3 text-sm outline-none transition-all duration-250 ease-in-out"
|
||||
bg="neutral-50 dark:neutral-800"
|
||||
hover="bg-neutral-200 dark:bg-neutral-700"
|
||||
>
|
||||
<div>
|
||||
{{ $t(text) }}
|
||||
<div v-if="description" text="sm neutral-500">
|
||||
{{ $t(description) }}
|
||||
</div>
|
||||
</div>
|
||||
<slot />
|
||||
</label>
|
||||
</template>
|
||||
@@ -0,0 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
import Bar from './Bar.vue'
|
||||
|
||||
import { Button } from '../../misc'
|
||||
|
||||
defineProps<{
|
||||
text: string
|
||||
icon: string
|
||||
description?: string
|
||||
}>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'click'): void
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Bar :text="text" :description="description">
|
||||
<Button @click="emits('click')">
|
||||
<slot />
|
||||
</Button>
|
||||
</Bar>
|
||||
</template>
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import Bar from './Bar.vue'
|
||||
|
||||
defineProps<{
|
||||
text: string
|
||||
iconOn: string
|
||||
@@ -9,21 +11,11 @@ const model = defineModel<boolean>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label
|
||||
class="w-full flex cursor-pointer items-center justify-between rounded-lg px-4 py-3 text-sm outline-none transition-all duration-250 ease-in-out"
|
||||
bg="neutral-50 dark:neutral-800"
|
||||
hover="bg-neutral-200 dark:bg-neutral-700"
|
||||
>
|
||||
<Bar :text="text" :description="description">
|
||||
<input v-model="model" :aria-checked="model" type="checkbox" hidden>
|
||||
<div>
|
||||
{{ $t(text) }}
|
||||
<div v-if="description" text="sm neutral-500">
|
||||
{{ $t(description) }}
|
||||
</div>
|
||||
</div>
|
||||
<Transition name="slide-away" mode="out-in">
|
||||
<div v-if="model" :class="iconOn" transition="all ease-in-out duration-250" />
|
||||
<div v-else :class="iconOff" transition="all ease-in-out duration-250" />
|
||||
</Transition>
|
||||
</label>
|
||||
</Bar>
|
||||
</template>
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export { default as Bar } from './Bar.vue'
|
||||
export { default as ButtonBar } from './ButtonBar.vue'
|
||||
export { default as CheckBar } from './CheckBar.vue'
|
||||
|
||||
Reference in New Issue
Block a user