From dec2b049243e8194ebe6b1af153c8ad5f9842875 Mon Sep 17 00:00:00 2001 From: Lilia_Chen Date: Thu, 12 Mar 2026 01:20:20 +0000 Subject: [PATCH] refactor: vrm performance tracing and better lifecycle management (#1194) * refactor: vrm performance tracing and better lifecycle management * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Rename lag-visualizer.vue to performance-visualizer.vue * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @sumimakito Co-authored-by: Makito * Apply suggestion from @sumimakito Co-authored-by: Makito * Apply suggestion from @sumimakito Co-authored-by: Makito * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * [autofix.ci] apply automated fixes * Apply suggestion from @Lilia-Chen * [autofix.ci] apply automated fixes * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * [autofix.ci] apply automated fixes * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * [autofix.ci] apply automated fixes * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * Apply suggestion from @Lilia-Chen * fix: some tiny bugs and model URL lifecycle * fix: TresCanvas size=0 bug * Delete .claude/settings.json * fix: some clean-up * [autofix.ci] apply automated fixes * fix: use screen to wrap up TresCanvas * Delete .claude/settings.json --------- Co-authored-by: Makito Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Neko --- apps/stage-pocket/package.json | 1 - .../src/pages/settings/system/developer.vue | 2 +- .../src/main/services/electron/index.ts | 1 + .../src/main/services/electron/window.ts | 35 +- apps/stage-tamagotchi/src/renderer/App.vue | 8 +- .../bridges/stage-three-runtime-trace.ts | 143 ++++ .../pages/devtools/lag-visualizer.vue | 26 - .../pages/devtools/performance-visualizer.vue | 198 +++++ .../src/renderer/pages/index.vue | 64 +- .../pages/settings/system/developer.vue | 26 +- .../stage-three-runtime-diagnostics.test.ts | 87 ++ .../stores/stage-three-runtime-diagnostics.ts | 488 +++++++++++ .../stores/stage-window-lifecycle.test.ts | 69 ++ .../renderer/stores/stage-window-lifecycle.ts | 62 ++ .../utils/stage-three-transparency.ts | 15 + apps/stage-tamagotchi/src/shared/eventa.ts | 53 ++ apps/stage-web/package.json | 1 - apps/stage-web/src/App.vue | 4 +- .../src/pages/settings/system/developer.vue | 2 +- packages/i18n/src/locales/en/settings.yaml | 2 +- packages/i18n/src/locales/es/settings.yaml | 2 +- packages/i18n/src/locales/ja/settings.yaml | 2 +- packages/i18n/src/locales/ko/settings.yaml | 2 +- packages/i18n/src/locales/ru/settings.yaml | 2 +- packages/i18n/src/locales/vi/settings.yaml | 2 +- .../i18n/src/locales/zh-Hans/settings.yaml | 2 +- .../i18n/src/locales/zh-Hant/settings.yaml | 2 +- .../Layouts/ViewControls/Inputs.vue | 24 +- packages/stage-ui-live2d/src/stores/live2d.ts | 8 +- .../package.json | 27 - .../src/blackboard.ts | 137 --- .../src/index.ts | 2 - .../src/types.ts | 71 -- .../tsconfig.json | 41 - packages/stage-ui-three/README.md | 96 +++ packages/stage-ui-three/package.json | 2 + .../src/components/Controls/OrbitControls.vue | 50 +- .../src/components/Model/VRMModel.vue | 784 +++++++++++++----- .../components/Model/vrm-instance-cache.ts | 82 ++ .../src/components/ThreeScene.vue | 605 ++++++++++---- .../src/composables/render-target.ts | 18 + packages/stage-ui-three/src/index.ts | 1 + .../stage-ui-three/src/stores/model-store.ts | 107 ++- .../stage-ui-three/src/trace/context.test.ts | 39 + packages/stage-ui-three/src/trace/context.ts | 53 ++ packages/stage-ui-three/src/trace/eventa.ts | 33 + packages/stage-ui-three/src/trace/index.ts | 4 + .../src/trace/snapshots.test.ts | 87 ++ .../stage-ui-three/src/trace/snapshots.ts | 105 +++ packages/stage-ui-three/src/trace/types.ts | 157 ++++ .../stage-ui-three/src/utils/vrm-preview.ts | 54 +- .../components/data-pane/property-color.vue | 4 +- .../components/data-pane/property-point.vue | 16 +- .../model-selector/model-selector-dialog.vue | 16 +- .../dialogs/model-selector/model-selector.vue | 22 +- .../settings/model-settings/index.vue | 40 +- .../scenarios/settings/model-settings/vrm.vue | 41 +- .../stage-ui/src/components/scenes/Stage.vue | 85 +- .../src/components/scenes/runtime.test.ts | 11 + .../stage-ui/src/components/scenes/runtime.ts | 10 + .../stage-ui/src/stores/perf-tracer-bridge.ts | 3 +- .../src/stores/settings/stage-model.ts | 65 +- .../src/components/form/range/round-range.vue | 17 +- pnpm-lock.yaml | 18 +- 64 files changed, 3325 insertions(+), 911 deletions(-) create mode 100644 apps/stage-tamagotchi/src/renderer/bridges/stage-three-runtime-trace.ts delete mode 100644 apps/stage-tamagotchi/src/renderer/pages/devtools/lag-visualizer.vue create mode 100644 apps/stage-tamagotchi/src/renderer/pages/devtools/performance-visualizer.vue create mode 100644 apps/stage-tamagotchi/src/renderer/stores/stage-three-runtime-diagnostics.test.ts create mode 100644 apps/stage-tamagotchi/src/renderer/stores/stage-three-runtime-diagnostics.ts create mode 100644 apps/stage-tamagotchi/src/renderer/stores/stage-window-lifecycle.test.ts create mode 100644 apps/stage-tamagotchi/src/renderer/stores/stage-window-lifecycle.ts create mode 100644 apps/stage-tamagotchi/src/renderer/utils/stage-three-transparency.ts delete mode 100644 packages/stage-ui-three-performance-runtime/package.json delete mode 100644 packages/stage-ui-three-performance-runtime/src/blackboard.ts delete mode 100644 packages/stage-ui-three-performance-runtime/src/index.ts delete mode 100644 packages/stage-ui-three-performance-runtime/src/types.ts delete mode 100644 packages/stage-ui-three-performance-runtime/tsconfig.json create mode 100644 packages/stage-ui-three/README.md create mode 100644 packages/stage-ui-three/src/components/Model/vrm-instance-cache.ts create mode 100644 packages/stage-ui-three/src/trace/context.test.ts create mode 100644 packages/stage-ui-three/src/trace/context.ts create mode 100644 packages/stage-ui-three/src/trace/eventa.ts create mode 100644 packages/stage-ui-three/src/trace/index.ts create mode 100644 packages/stage-ui-three/src/trace/snapshots.test.ts create mode 100644 packages/stage-ui-three/src/trace/snapshots.ts create mode 100644 packages/stage-ui-three/src/trace/types.ts create mode 100644 packages/stage-ui/src/components/scenes/runtime.test.ts create mode 100644 packages/stage-ui/src/components/scenes/runtime.ts diff --git a/apps/stage-pocket/package.json b/apps/stage-pocket/package.json index b9b1cd1f4..0635649d4 100644 --- a/apps/stage-pocket/package.json +++ b/apps/stage-pocket/package.json @@ -39,7 +39,6 @@ "@proj-airi/stage-shared": "workspace:^", "@proj-airi/stage-ui": "workspace:^", "@proj-airi/stage-ui-three": "workspace:^", - "@proj-airi/stage-ui-three-performance-runtime": "workspace:^", "@proj-airi/stream-kit": "workspace:^", "@proj-airi/ui": "workspace:^", "@proj-airi/ui-transitions": "workspace:^", diff --git a/apps/stage-pocket/src/pages/settings/system/developer.vue b/apps/stage-pocket/src/pages/settings/system/developer.vue index 9aaff3cf4..ad1ef395d 100644 --- a/apps/stage-pocket/src/pages/settings/system/developer.vue +++ b/apps/stage-pocket/src/pages/settings/system/developer.vue @@ -70,7 +70,7 @@ const menu = computed(() => [ }, { title: 'Performance Playground', - description: 'Test performance runtime: VRM expressions + TTS lip sync', + description: 'VRM expressions + TTS lip sync playground', icon: 'i-solar:sledgehammer-bold-duotone', to: '/devtools/performance-playground', }, diff --git a/apps/stage-tamagotchi/src/main/services/electron/index.ts b/apps/stage-tamagotchi/src/main/services/electron/index.ts index 082cbd77e..5a413509d 100644 --- a/apps/stage-tamagotchi/src/main/services/electron/index.ts +++ b/apps/stage-tamagotchi/src/main/services/electron/index.ts @@ -1,4 +1,5 @@ export * from './app' export * from './auto-updater' export * from './screen' + export * from './window' diff --git a/apps/stage-tamagotchi/src/main/services/electron/window.ts b/apps/stage-tamagotchi/src/main/services/electron/window.ts index 259b32a82..815b025d1 100644 --- a/apps/stage-tamagotchi/src/main/services/electron/window.ts +++ b/apps/stage-tamagotchi/src/main/services/electron/window.ts @@ -1,15 +1,37 @@ import type { createContext } from '@moeru/eventa/adapters/electron/main' import type { BrowserWindow } from 'electron' +import type { ElectronWindowLifecycleState } from '../../../shared/eventa' + import { defineInvokeHandler } from '@moeru/eventa' import { bounds, startLoopGetBounds } from '@proj-airi/electron-eventa' import { createRendererLoop, safeClose } from '@proj-airi/electron-vueuse/main' -import { electron, electronWindowClose, electronWindowSetAlwaysOnTop } from '../../../shared/eventa' +import { + electron, + electronGetWindowLifecycleState, + electronWindowClose, + electronWindowLifecycleChanged, + electronWindowSetAlwaysOnTop, +} from '../../../shared/eventa' import { onAppBeforeQuit, onAppWindowAllClosed } from '../../libs/bootkit/lifecycle' import { resizeWindowByDelta } from '../../windows/shared/window' export function createWindowService(params: { context: ReturnType['context'], window: BrowserWindow }) { + function getWindowLifecycleState(reason: ElectronWindowLifecycleState['reason']): ElectronWindowLifecycleState { + return { + focused: params.window.isFocused(), + minimized: params.window.isMinimized(), + reason, + updatedAt: Date.now(), + visible: params.window.isVisible(), + } + } + + function emitWindowLifecycle(reason: ElectronWindowLifecycleState['reason']) { + params.context.emit(electronWindowLifecycleChanged, getWindowLifecycleState(reason)) + } + const { start, stop } = createRendererLoop({ window: params.window, run: () => { @@ -20,6 +42,17 @@ export function createWindowService(params: { context: ReturnType stop()) onAppBeforeQuit(() => stop()) defineInvokeHandler(params.context, startLoopGetBounds, () => start()) + defineInvokeHandler(params.context, electronGetWindowLifecycleState, (_, options) => { + if (params.window.webContents.id === options?.raw.ipcMainEvent.sender.id) + return getWindowLifecycleState('snapshot') + }) + + params.window.on('show', () => emitWindowLifecycle('show')) + params.window.on('hide', () => emitWindowLifecycle('hide')) + params.window.on('minimize', () => emitWindowLifecycle('minimize')) + params.window.on('restore', () => emitWindowLifecycle('restore')) + params.window.on('focus', () => emitWindowLifecycle('focus')) + params.window.on('blur', () => emitWindowLifecycle('blur')) defineInvokeHandler(params.context, electron.window.getBounds, (_, options) => { if (params.window.webContents.id === options?.raw.ipcMainEvent.sender.id) { diff --git a/apps/stage-tamagotchi/src/renderer/App.vue b/apps/stage-tamagotchi/src/renderer/App.vue index ad8cd7c1f..a9edcb025 100644 --- a/apps/stage-tamagotchi/src/renderer/App.vue +++ b/apps/stage-tamagotchi/src/renderer/App.vue @@ -41,7 +41,9 @@ import { pluginProtocolListProviders, pluginProtocolListProvidersEventName, } from '../shared/eventa' +import { initializeStageThreeRuntimeTraceBridge } from './bridges/stage-three-runtime-trace' import { useServerChannelSettingsStore } from './stores/settings/server-channel' +import { useStageWindowLifecycleStore } from './stores/stage-window-lifecycle' const { isDark: dark } = useTheme() const i18n = useI18n() @@ -58,9 +60,11 @@ const serverChannelStore = useModsServerChannelStore() const characterOrchestratorStore = useCharacterOrchestratorStore() const analyticsStore = useSharedAnalyticsStore() const pluginHostInspectorStore = usePluginHostInspectorStore() -usePerfTracerBridgeStore() - +const stageWindowLifecycleStore = useStageWindowLifecycleStore() const context = useElectronEventaContext() +usePerfTracerBridgeStore() +initializeStageThreeRuntimeTraceBridge() +void stageWindowLifecycleStore.initializeWindowLifecycleBridge() const getServerChannelConfig = useElectronEventaInvoke(electronGetServerChannelConfig) const listPlugins = useElectronEventaInvoke(electronPluginList) const setPluginEnabled = useElectronEventaInvoke(electronPluginSetEnabled) diff --git a/apps/stage-tamagotchi/src/renderer/bridges/stage-three-runtime-trace.ts b/apps/stage-tamagotchi/src/renderer/bridges/stage-three-runtime-trace.ts new file mode 100644 index 000000000..01bbc3801 --- /dev/null +++ b/apps/stage-tamagotchi/src/renderer/bridges/stage-three-runtime-trace.ts @@ -0,0 +1,143 @@ +import type { Eventa } from '@moeru/eventa' + +import type { StageThreeRuntimeTraceEnvelope } from '../../shared/eventa' + +import { createContext as createBroadcastChannelContext } from '@moeru/eventa/adapters/broadcast-channel' +import { + acquireStageThreeRuntimeTrace, + getStageThreeRuntimeTraceContext, + stageThreeTraceHitTestReadEvent, + stageThreeTraceRenderInfoEvent, + stageThreeTraceVrmDisposeEndEvent, + stageThreeTraceVrmDisposeStartEvent, + stageThreeTraceVrmLoadEndEvent, + stageThreeTraceVrmLoadErrorEvent, + stageThreeTraceVrmLoadStartEvent, + stageThreeTraceVrmUpdateFrameEvent, +} from '@proj-airi/stage-ui-three/trace' + +import { + stageThreeRuntimeTraceForwardedEvent, + stageThreeRuntimeTraceRemoteDisableEvent, + stageThreeRuntimeTraceRemoteEnableEvent, +} from '../../shared/eventa' + +const STAGE_THREE_RUNTIME_TRACE_CHANNEL = 'airi::stage-three-runtime-trace' +const relayTraceLeaseToken = 'stage-three-runtime-trace:broadcast-relay' +const localTraceContext = getStageThreeRuntimeTraceContext() +const instanceId = Math.random().toString(36).slice(2, 10) +const replayOrder: Array = [ + 'vrm-load-start', + 'vrm-load-end', + 'vrm-load-error', + 'vrm-dispose-start', + 'vrm-dispose-end', + 'three-hit-test-read', + 'three-render-info', + 'vrm-update-frame', +] + +let initialized = false +let broadcastContext: ReturnType['context'] | undefined +let channel: BroadcastChannel | undefined +let releaseRelayTrace: (() => void) | undefined +const remoteSubscribers = new Set() +const latestEnvelopes = new Map() + +function getChannel() { + channel ??= new BroadcastChannel(STAGE_THREE_RUNTIME_TRACE_CHANNEL) + return channel +} + +export function getStageThreeRuntimeTraceBroadcastContext() { + broadcastContext ??= createBroadcastChannelContext(getChannel()).context + return broadcastContext +} + +export function getStageThreeRuntimeTraceBroadcastOriginId() { + return instanceId +} + +function applyCollectionState(active: boolean) { + if (active) { + releaseRelayTrace ??= acquireStageThreeRuntimeTrace(relayTraceLeaseToken) + return + } + + releaseRelayTrace?.() + releaseRelayTrace = undefined +} + +function emitTraceEnvelope(envelope: StageThreeRuntimeTraceEnvelope) { + latestEnvelopes.set(envelope.type, envelope) + getStageThreeRuntimeTraceBroadcastContext().emit(stageThreeRuntimeTraceForwardedEvent, { + envelope, + origin: instanceId, + }) +} + +function replayLatestTraceEnvelopes() { + const context = getStageThreeRuntimeTraceBroadcastContext() + + for (const type of replayOrder) { + const envelope = latestEnvelopes.get(type) + if (!envelope) + continue + + context.emit(stageThreeRuntimeTraceForwardedEvent, { + envelope, + origin: instanceId, + }) + } +} + +function subscribeTraceEvent(eventa: Eventa, createEnvelope: (payload: T) => StageThreeRuntimeTraceEnvelope) { + localTraceContext.on(eventa, (event) => { + if (!event?.body) + return + + emitTraceEnvelope(createEnvelope(event.body)) + }) +} + +export async function setStageThreeRuntimeTraceRemoteSubscription(active: boolean) { + const eventa = active ? stageThreeRuntimeTraceRemoteEnableEvent : stageThreeRuntimeTraceRemoteDisableEvent + getStageThreeRuntimeTraceBroadcastContext().emit(eventa, { origin: instanceId }) +} + +export function initializeStageThreeRuntimeTraceBridge() { + if (initialized) + return + + initialized = true + + const context = getStageThreeRuntimeTraceBroadcastContext() + + context.on(stageThreeRuntimeTraceRemoteEnableEvent, (event) => { + const origin = event?.body?.origin + if (!origin || origin === instanceId) + return + + remoteSubscribers.add(origin) + applyCollectionState(remoteSubscribers.size > 0) + replayLatestTraceEnvelopes() + }) + + context.on(stageThreeRuntimeTraceRemoteDisableEvent, (event) => { + const origin = event?.body?.origin + if (!origin || origin === instanceId) + return + + remoteSubscribers.delete(origin) + applyCollectionState(remoteSubscribers.size > 0) + }) + + subscribeTraceEvent(stageThreeTraceRenderInfoEvent, payload => ({ type: 'three-render-info', payload })) + subscribeTraceEvent(stageThreeTraceHitTestReadEvent, payload => ({ type: 'three-hit-test-read', payload })) + subscribeTraceEvent(stageThreeTraceVrmUpdateFrameEvent, payload => ({ type: 'vrm-update-frame', payload })) + subscribeTraceEvent(stageThreeTraceVrmLoadStartEvent, payload => ({ type: 'vrm-load-start', payload })) + subscribeTraceEvent(stageThreeTraceVrmLoadEndEvent, payload => ({ type: 'vrm-load-end', payload })) + subscribeTraceEvent(stageThreeTraceVrmLoadErrorEvent, payload => ({ type: 'vrm-load-error', payload })) + subscribeTraceEvent(stageThreeTraceVrmDisposeStartEvent, payload => ({ type: 'vrm-dispose-start', payload })) + subscribeTraceEvent(stageThreeTraceVrmDisposeEndEvent, payload => ({ type: 'vrm-dispose-end', payload })) +} diff --git a/apps/stage-tamagotchi/src/renderer/pages/devtools/lag-visualizer.vue b/apps/stage-tamagotchi/src/renderer/pages/devtools/lag-visualizer.vue deleted file mode 100644 index c8d4abb63..000000000 --- a/apps/stage-tamagotchi/src/renderer/pages/devtools/lag-visualizer.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - - - -meta: - layout: settings - titleKey: tamagotchi.settings.devtools.pages.lag-visualizer.title - subtitleKey: tamagotchi.settings.devtools.title - diff --git a/apps/stage-tamagotchi/src/renderer/pages/devtools/performance-visualizer.vue b/apps/stage-tamagotchi/src/renderer/pages/devtools/performance-visualizer.vue new file mode 100644 index 000000000..c897307d8 --- /dev/null +++ b/apps/stage-tamagotchi/src/renderer/pages/devtools/performance-visualizer.vue @@ -0,0 +1,198 @@ + + + + + +meta: + layout: settings + titleKey: tamagotchi.settings.devtools.pages.performance-visualizer.title + subtitleKey: tamagotchi.settings.devtools.title + diff --git a/apps/stage-tamagotchi/src/renderer/pages/index.vue b/apps/stage-tamagotchi/src/renderer/pages/index.vue index ac996f965..47244ef3a 100644 --- a/apps/stage-tamagotchi/src/renderer/pages/index.vue +++ b/apps/stage-tamagotchi/src/renderer/pages/index.vue @@ -32,7 +32,9 @@ import ResourceStatusIsland from '../components/stage-islands/resource-status-is import { electronOpenOnboarding } from '../../shared/eventa' import { useControlsIslandStore } from '../stores/controls-island' +import { useStageWindowLifecycleStore } from '../stores/stage-window-lifecycle' import { useWindowStore } from '../stores/window' +import { shouldSampleStageTransparency } from '../utils/stage-three-transparency' const controlsIslandRef = ref>() const widgetStageRef = ref>() @@ -68,9 +70,20 @@ const isTransparentByThree = useThreeSceneIsTransparentAtPoint( ) const { stageModelRenderer } = storeToRefs(useSettings()) +const { stagePaused } = storeToRefs(useStageWindowLifecycleStore()) +const { fadeOnHoverEnabled } = storeToRefs(useControlsIslandStore()) +const shouldUseThreeTransparencyHitTest = computed(() => shouldSampleStageTransparency({ + componentState: componentStateStage.value, + fadeOnHoverEnabled: fadeOnHoverEnabled.value, + stageModelRenderer: stageModelRenderer.value, + stagePaused: stagePaused.value, +})) const isTransparent = computed(() => { + if (stagePaused.value || componentStateStage.value !== 'mounted' || !fadeOnHoverEnabled.value) + return true + if (stageModelRenderer.value === 'vrm') - return isTransparentByThree.value + return shouldUseThreeTransparencyHitTest.value ? isTransparentByThree.value : true if (stageModelRenderer.value === 'live2d') return isTransparentByPixels.value @@ -86,7 +99,6 @@ const setIgnoreMouseEvents = useElectronEventaInvoke(electron.window.setIgnoreMo const live2dStore = useLive2d() const { scale, positionInPercentageString } = storeToRefs(live2dStore) const { live2dLookAtX, live2dLookAtY } = storeToRefs(useWindowStore()) -const { fadeOnHoverEnabled } = storeToRefs(useControlsIslandStore()) watch(componentStateStage, () => isLoading.value = componentStateStage.value !== 'mounted', { immediate: true }) @@ -96,7 +108,15 @@ const { pause, resume } = watch(isTransparent, (transparent) => { const hearingDialogOpen = computed(() => controlsIslandRef.value?.hearingDialogOpen ?? false) -watch([isOutsideFor250Ms, isAroundWindowBorderFor250Ms, isOutsideWindow, isTransparent, hearingDialogOpen, fadeOnHoverEnabled], () => { +watch([isOutsideFor250Ms, isAroundWindowBorderFor250Ms, isOutsideWindow, isTransparent, hearingDialogOpen, fadeOnHoverEnabled, stagePaused], () => { + if (stagePaused.value) { + isIgnoringMouseEvents.value = false + shouldFadeOnCursorWithin.value = false + setIgnoreMouseEvents([false, { forward: true }]) + pause() + return + } + if (hearingDialogOpen.value) { // Hearing dialog/drawer is open; keep window interactive isIgnoringMouseEvents.value = false @@ -338,8 +358,8 @@ watch([stream, () => vadLoaded.value], async ([s, loaded]) => { relative z-2 h-full overflow-hidden rounded-xl transition="opacity duration-500 ease-in-out" > +
vadLoaded.value], async ([s, loaded]) => { />
-
-
+ +
+
-
- Loading... -
+ Loading...
diff --git a/apps/stage-tamagotchi/src/renderer/pages/settings/system/developer.vue b/apps/stage-tamagotchi/src/renderer/pages/settings/system/developer.vue index a3aca6025..ccc4e9f1c 100644 --- a/apps/stage-tamagotchi/src/renderer/pages/settings/system/developer.vue +++ b/apps/stage-tamagotchi/src/renderer/pages/settings/system/developer.vue @@ -4,11 +4,13 @@ import { ButtonBar, CheckBar, IconItem } from '@proj-airi/stage-ui/components' import { useSettings } from '@proj-airi/stage-ui/stores/settings' import { computed } from 'vue' import { useI18n } from 'vue-i18n' +import { useRouter } from 'vue-router' import { electronOpenDevtoolsWindow, electronOpenMainDevtools } from '../../../../shared/eventa' const { t } = useI18n() const settings = useSettings() +const router = useRouter() const menu = computed(() => [ { @@ -80,7 +82,7 @@ const menu = computed(() => [ ]) const openDevTools = useElectronEventaInvoke(electronOpenMainDevtools) -const openMarkdownStressWindow = useElectronEventaInvoke(electronOpenDevtoolsWindow) +const openDevtoolsWindow = useElectronEventaInvoke(electronOpenDevtoolsWindow)