Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5d39a93
feat(stage-tamagotchi): add auto-hide controls island settings
leaft Mar 29, 2026
bcdbad7
feat(stage-tamagotchi): watch show delay and hide delay
leaft Mar 29, 2026
2f5562b
feat(settings): add auto-hide opacity setting for controls island
leaft Mar 31, 2026
98981fd
feat(settings): remove controls island icon size setting and simplify…
leaft Mar 31, 2026
91c6ad2
feat(settings): update auto-hide controls island setting to use isSta…
leaft Mar 31, 2026
a2b65bc
feat(tests): add unit tests for settings-controls-island store
leaft Mar 31, 2026
a41440a
feat(settings): refactor auto-hide logic using refDebounced for impro…
leaft Mar 31, 2026
b944bd8
feat(settings): update auto-hide logic to use computed for dynamic de…
leaft Mar 31, 2026
0597d3f
feat(settings): update auto-hide delay logic to use configurable sett…
leaft Mar 31, 2026
c29304c
feat(stage-ui): remove redundant background color class from island div
leaft Apr 1, 2026
577b945
feat(controls-island): refactor auto-hide logic to use useTimeoutFn f…
leaft Apr 1, 2026
a0e185e
feat(controls-island): implement auto-hide and auto-collapse composab…
leaft Apr 1, 2026
72492c1
fix(controls-island): correct import name for auto-hide composable
leaft Apr 1, 2026
4c4ea5c
feat(tests): add unit tests for useControlsIslandAutoHide and useCont…
leaft Apr 1, 2026
33ea17a
refactor(useControlsIslandAutoHide): remove unused variables and stre…
leaft Apr 1, 2026
6018619
feat(controls-island): add useControlsIslandCollapse composable and r…
leaft Apr 1, 2026
a76fb38
[claudesquad] update from 'beautify-feat' on 02 Apr 26 15:47 CST (pau…
leaft Apr 2, 2026
9d35be2
feat(settings): expose auto-hide controls island settings in deprecat…
leaft Apr 2, 2026
7773b30
fix(controls-island): prevent flicker during quick mouse movements
leaft Apr 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script setup lang="ts">
import { defineInvoke } from '@moeru/eventa'
import { useElectronEventaContext, useElectronEventaInvoke, useElectronMouseInElement } from '@proj-airi/electron-vueuse'
import { useSettings, useSettingsAudioDevice } from '@proj-airi/stage-ui/stores/settings'
import { useControlsIslandAutoHide, useControlsIslandCollapse } from '@proj-airi/stage-ui/composables'
import { useSettings, useSettingsAudioDevice, useSettingsControlsIsland } from '@proj-airi/stage-ui/stores/settings'
import { useTheme } from '@proj-airi/ui'
import { refDebounced, useIntervalFn } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { computed, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
Expand All @@ -29,9 +29,11 @@ const { t } = useI18n()

const settingsAudioDeviceStore = useSettingsAudioDevice()
const settingsStore = useSettings()
const settingsControlsIslandStore = useSettingsControlsIsland()
const context = useElectronEventaContext()
const { enabled } = storeToRefs(settingsAudioDeviceStore)
const { alwaysOnTop, controlsIslandIconSize } = storeToRefs(settingsStore)
const { alwaysOnTop } = storeToRefs(settingsStore)
const { autoHideControlsIsland, autoHideDelay, autoShowDelay, autoHideOpacity } = storeToRefs(settingsControlsIslandStore)
const openSettings = useElectronEventaInvoke(electronOpenSettings)
const openChat = useElectronEventaInvoke(electronOpenChat)
const isLinux = useElectronEventaInvoke(electron.app.isLinux)
Expand All @@ -46,7 +48,10 @@ const blockingOverlays = reactive(new Set<string>())
const isBlocked = computed(() => blockingOverlays.size > 0)

function setOverlay(key: string, active: boolean) {
active ? blockingOverlays.add(key) : blockingOverlays.delete(key)
if (active)
blockingOverlays.add(key)
else
blockingOverlays.delete(key)
}

// Expose for parent (e.g. to disable click-through when a dialog is open)
Expand All @@ -56,12 +61,31 @@ defineExpose({
})

const { isOutside } = useElectronMouseInElement(islandRef)
const isOutsideAfter2seconds = refDebounced(isOutside, 1500)

watch(isOutsideAfter2seconds, (outside) => {
if (outside && expanded.value && !isBlocked.value) {
expanded.value = false
}
// Auto-hide / Auto-show behavior
const { isHidden, hiddenOpacity } = useControlsIslandAutoHide({
autoHideControlsIsland,
autoHideDelay,
autoShowDelay,
autoHideOpacity,
isOutside,
isBlocked,
expanded,
})

// Auto-collapse behavior
const { startCollapse, stopCollapse } = useControlsIslandCollapse({
autoHideDelay,
autoHideControlsIsland,
expanded,
isBlocked,
})

// Watch mouse position to trigger collapse
watch(isOutside, (val) => {
stopCollapse()
if (val)
startCollapse()
})

watch(expanded, (isExpanded) => {
Expand All @@ -70,12 +94,6 @@ watch(expanded, (isExpanded) => {
}
})

useIntervalFn(() => {
if (expanded.value && isOutside.value && !isBlocked.value) {
expanded.value = false
}
}, 1500)

// Apply alwaysOnTop on mount and when it changes
watch(alwaysOnTop, (val) => {
setAlwaysOnTop(val)
Expand All @@ -85,31 +103,13 @@ function toggleAlwaysOnTop() {
alwaysOnTop.value = !alwaysOnTop.value
}

// Grouped classes for icon / border / padding and combined style class
const adjustStyleClasses = computed(() => {
let isLarge: boolean

// Determine size based on setting
switch (controlsIslandIconSize.value) {
case 'large':
isLarge = true
break
case 'small':
isLarge = false
break
case 'auto':
default:
// Fixed to large for better visibility in the new layout,
// can be changed to windowHeight based check if absolutely needed.
isLarge = true
break
}

const icon = isLarge ? 'size-5' : 'size-3'
const border = isLarge ? 'border-2' : 'border-0'
const padding = isLarge ? 'p-2' : 'p-0.5'
return { icon, border, padding, button: `${border} ${padding}` }
})
// Static style classes for icon / border / padding
const adjustStyleClasses = {
icon: 'size-5',
border: 'border-2',
padding: 'p-2',
button: 'border-2 p-2',
}

/**
* This is a know issue (or expected behavior maybe) to Electron.
Expand All @@ -125,7 +125,14 @@ function refreshWindow() {
</script>

<template>
<div ref="islandRef" fixed bottom-2 right-2>
<div
ref="islandRef"
fixed bottom-2 right-2
:style="autoHideControlsIsland ? { opacity: isHidden ? hiddenOpacity : 1 } : {}"
:class="[
autoHideControlsIsland ? 'transition-opacity duration-300' : '',
]"
>
<div flex flex-col items-end gap-1>
<!-- iOS Style Drawer Panel -->
<Transition
Expand Down
9 changes: 9 additions & 0 deletions packages/i18n/src/locales/en/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ controls-island:
auto: Auto (responsive)
large: Large (default)
small: Small
auto-hide:
title: Auto-hide controls island
description: Hide controls island after mouse leaves, show after mouse enters
hide-delay:
title: Hide delay
show-delay:
title: Show delay
hidden-opacity:
title: Hidden opacity
analytics:
notice:
title: Usage analytics
Expand Down
9 changes: 9 additions & 0 deletions packages/i18n/src/locales/es/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ controls-island:
auto: Automático (adaptativo)
large: Grande (predeterminado)
small: Pequeño
auto-hide:
title: Ocultar automáticamente la isla de controles
description: Ocultar la isla de controles cuando el ratón sale, mostrar cuando entra
hide-delay:
title: Retraso al ocultar
show-delay:
title: Retraso al mostrar
hidden-opacity:
title: Opacidad al ocultar
analytics:
notice:
title: Análisis de uso
Expand Down
9 changes: 9 additions & 0 deletions packages/i18n/src/locales/fr/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ controls-island:
auto: Auto (réactif)
large: Grande (défaut)
small: Petite
auto-hide:
title: Masquer automatiquement l'îlot de contrôle
description: Masquer l'îlot de contrôle lorsque la souris sort, afficher cuando la souris entre
hide-delay:
title: Délai de masquage
show-delay:
title: Délai d'affichage
hidden-opacity:
title: Opacité lors du masquage
analytics:
notice:
title: Usage analytics
Expand Down
9 changes: 9 additions & 0 deletions packages/i18n/src/locales/ja/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ controls-island:
auto: 自動 (レスポンシブ)
large: 大 (デフォルト)
small: 小
auto-hide:
title: コントロールアイランドを自動非表示
description: マウ스가離れたときにコントロールアイランドを非表示にし、 入ると表示します
hide-delay:
title: 非表示までの遅延
show-delay:
title: 表示までの遅延
hidden-opacity:
title: 非表示時の透明度
analytics:
notice:
title: Usage analytics
Expand Down
9 changes: 9 additions & 0 deletions packages/i18n/src/locales/ko/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ controls-island:
auto: 자동 (능동적)
large: 크게 (기본값)
small: 작게
auto-hide:
title: 컨트롤 아일랜드 자동 숨기기
description: 마우스가 떠날 때 컨트롤 아일랜드를 숨기고, 들어오면 표시합니다
hide-delay:
title: 숨기기 지연
show-delay:
title: 표시 지연
hidden-opacity:
title: 숨기기 시 투명도
analytics:
notice:
title: Usage analytics
Expand Down
9 changes: 9 additions & 0 deletions packages/i18n/src/locales/ru/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ controls-island:
auto: Автоматически (отвечает)
large: Большой (по умолчанию)
small: Маленький
auto-hide:
title: Автоматически скрывать остров управления
description: Скрывать остров управления, когда мышь уходит, показывать, когда наводится
hide-delay:
title: Задержка скрытия
show-delay:
title: Задержка показа
hidden-opacity:
title: Прозрачность при скрытии
analytics:
notice:
title: Usage analytics
Expand Down
9 changes: 9 additions & 0 deletions packages/i18n/src/locales/vi/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ controls-island:
auto: Tự động (thích ứng)
large: Lớn (mặc định)
small: Nhỏ
auto-hide:
title: Tự động ẩn đảo điều khiển
description: Ẩn đảo điều khiển khi chuột rời đi, hiển thị khi chuột di vào
hide-delay:
title: Độ trễ ẩn
show-delay:
title: Độ trễ hiển thị
hidden-opacity:
title: Độ mờ khi ẩn
analytics:
notice:
title: Usage analytics
Expand Down
9 changes: 9 additions & 0 deletions packages/i18n/src/locales/zh-Hans/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ controls-island:
auto: 自动(响应式)
large: 大(默认)
small: 小
auto-hide:
title: 自动隐藏控制岛
description: 鼠标离开后隐藏控制岛,鼠标进入后显示
hide-delay:
title: 隐藏延迟
show-delay:
title: 显示延迟
hidden-opacity:
title: 隐藏时透明度
analytics:
notice:
title: 使用数据及崩溃分析
Expand Down
9 changes: 9 additions & 0 deletions packages/i18n/src/locales/zh-Hant/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ controls-island:
auto: 自動(回應式)
large: 大(默認)
small: 小
auto-hide:
title: 自動隱藏控制島
description: 滑鼠離開後隱藏控制島,滑鼠進入後顯示
hide-delay:
title: 隱藏延遲
show-delay:
title: 顯示延遲
hidden-opacity:
title: 隱藏時透明度
analytics:
notice:
title: Usage analytics
Expand Down
76 changes: 58 additions & 18 deletions packages/stage-pages/src/components/settings-general-fields.vue
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
<script setup lang="ts">
import { all } from '@proj-airi/i18n'
import { isStageTamagotchi } from '@proj-airi/stage-shared'
import { useAnalytics } from '@proj-airi/stage-ui/composables/use-analytics'
import { isPosthogAvailableInBuild } from '@proj-airi/stage-ui/stores/analytics'
import { useSettings } from '@proj-airi/stage-ui/stores/settings'
import { FieldCheckbox, FieldCombobox, useTheme } from '@proj-airi/ui'
import { useSettings, useSettingsControlsIsland } from '@proj-airi/stage-ui/stores/settings'
import { FieldCheckbox, FieldCombobox, FieldRange, useTheme } from '@proj-airi/ui'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'

const props = withDefaults(defineProps<{
needsControlsIslandIconSizeSetting?: boolean
needsAutoHideControlsIslandSetting?: boolean
}>(), {
needsControlsIslandIconSizeSetting: import.meta.env.RUNTIME_ENVIRONMENT === 'electron',
needsAutoHideControlsIslandSetting: isStageTamagotchi(),
})

const settings = useSettings()
const settingsControlsIsland = useSettingsControlsIsland()

const showControlsIsland = computed(() => props.needsControlsIslandIconSizeSetting)
const showAutoHideControlsIsland = computed(() => props.needsAutoHideControlsIslandSetting)
const showAnalyticsSettings = computed(() => isPosthogAvailableInBuild())
const analyticsToggleValue = computed({
get: () => showAnalyticsSettings.value ? settings.analyticsEnabled : false,
Expand Down Expand Up @@ -59,24 +61,62 @@ const languages = computed(() => {
:options="languages"
/>

<FieldCombobox
v-if="showControlsIsland"
v-model="settings.controlsIslandIconSize"
<FieldCheckbox
v-if="showAutoHideControlsIsland"
v-model="settingsControlsIsland.autoHideControlsIsland"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + (4 * 10)"
:delay="4 * 50"
:class="['transition-all', 'ease-in-out', 'duration-250']"
:label="t('settings.controls-island.icon-size.title')"
:description="t('settings.controls-island.icon-size.description')"
:options="[
{ value: 'auto', label: t('settings.controls-island.icon-size.auto') },
{ value: 'large', label: t('settings.controls-island.icon-size.large') },
{ value: 'small', label: t('settings.controls-island.icon-size.small') },
]"
:duration="250 + (5 * 10)"
:delay="5 * 50"
:label="t('settings.controls-island.auto-hide.title')"
:description="t('settings.controls-island.auto-hide.description')"
/>

<template v-if="showAutoHideControlsIsland && settingsControlsIsland.autoHideControlsIsland">
<FieldRange
v-model.number="settingsControlsIsland.autoHideDelay"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + (6 * 10)"
:delay="6 * 50"
:min="0"
:max="5"
:step="0.1"
:format-value="v => `${v}s`"
:label="t('settings.controls-island.hide-delay.title')"
/>

<FieldRange
v-model.number="settingsControlsIsland.autoShowDelay"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + (7 * 10)"
:delay="7 * 50"
:min="0"
:max="5"
:step="0.1"
:format-value="v => `${v}s`"
:label="t('settings.controls-island.show-delay.title')"
/>

<FieldRange
v-model.number="settingsControlsIsland.autoHideOpacity"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + (8 * 10)"
:delay="8 * 50"
:min="0"
:max="100"
:step="1"
:format-value="v => `${v}%`"
:label="t('settings.controls-island.hidden-opacity.title')"
/>
</template>

<FieldCheckbox
v-model="analyticsToggleValue"
v-motion
Expand Down
2 changes: 2 additions & 0 deletions packages/stage-ui/src/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export * from './use-async-state'
export * from './use-breakpoints'
export * from './use-build-info'
export * from './use-chat-session/summary'
export * from './use-controls-island-auto-hide'
export * from './use-controls-island-collapse'
export * from './use-optimistic'
export * from './use-scroll-to-hash'
export * from './vision'
Expand Down
Loading
Loading