feat(stage-tamagotchi): add icon size settings for tamagotchi (#940)
This commit is contained in:
+19
-2
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { defineInvoke } from '@moeru/eventa'
|
||||
import { useSettingsAudioDevice } from '@proj-airi/stage-ui/stores/settings'
|
||||
import { useSettings, useSettingsAudioDevice } from '@proj-airi/stage-ui/stores/settings'
|
||||
import { useTheme } from '@proj-airi/ui'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import { storeToRefs } from 'pinia'
|
||||
@@ -21,8 +21,10 @@ const { isDark, toggleDark } = useTheme()
|
||||
const { t } = useI18n()
|
||||
|
||||
const settingsAudioDeviceStore = useSettingsAudioDevice()
|
||||
const settingsStore = useSettings()
|
||||
const context = useElectronEventaContext()
|
||||
const { enabled } = storeToRefs(settingsAudioDeviceStore)
|
||||
const { controlsIslandIconSize } = storeToRefs(settingsStore)
|
||||
const openSettings = useElectronEventaInvoke(electronOpenSettings)
|
||||
const openChat = useElectronEventaInvoke(electronOpenChat)
|
||||
|
||||
@@ -35,7 +37,22 @@ const LARGE_THRESHOLD = ICON_PLACEHOLDER_PX * BUTTON_COUNT
|
||||
|
||||
// Grouped classes for icon / border / padding and combined style class
|
||||
const adjustStyleClasses = computed(() => {
|
||||
const isLarge = windowHeight.value > LARGE_THRESHOLD
|
||||
let isLarge: boolean
|
||||
|
||||
// Determine size based on setting
|
||||
switch (controlsIslandIconSize.value) {
|
||||
case 'large':
|
||||
isLarge = true
|
||||
break
|
||||
case 'small':
|
||||
isLarge = false
|
||||
break
|
||||
case 'auto':
|
||||
default:
|
||||
isLarge = windowHeight.value > LARGE_THRESHOLD
|
||||
break
|
||||
}
|
||||
|
||||
const icon = isLarge ? 'size-5' : 'size-3'
|
||||
const border = isLarge ? 'border-2' : 'border-0'
|
||||
const padding = isLarge ? 'p-2' : 'p-0.5'
|
||||
|
||||
@@ -37,6 +37,13 @@ language:
|
||||
description: >
|
||||
Change the language of the AIRI interface. This will not affect the language
|
||||
of the character's responses.
|
||||
controls-island:
|
||||
icon-size:
|
||||
title: Controls Island Icon Size
|
||||
description: Adjust the size of the controls island icons
|
||||
auto: Auto (responsive)
|
||||
large: Large (default)
|
||||
small: Small
|
||||
live2d:
|
||||
change-model:
|
||||
from-file: Load from File
|
||||
|
||||
@@ -34,6 +34,13 @@ language:
|
||||
title: 语言
|
||||
description: >
|
||||
切换显示界面的语言
|
||||
controls-island:
|
||||
icon-size:
|
||||
title: 侧边控制栏图标大小
|
||||
description: 调整侧边控制栏图标的大小
|
||||
auto: 自动(响应式)
|
||||
large: 默认
|
||||
small: 小图标
|
||||
live2d:
|
||||
change-model:
|
||||
from-file: 从文件加载
|
||||
|
||||
@@ -5,6 +5,14 @@ import { FieldCheckbox, FieldSelect, useTheme } from '@proj-airi/ui'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
needscontrolsIslandIconSizeSetting?: boolean
|
||||
}>(), {
|
||||
needscontrolsIslandIconSizeSetting: import.meta.env.RUNTIME_ENVIRONMENT === 'electron',
|
||||
})
|
||||
|
||||
const showControlsIsland = computed(() => props.needscontrolsIslandIconSizeSetting)
|
||||
|
||||
const settings = useSettings()
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -40,7 +48,26 @@ const languages = computed(() => {
|
||||
transition="all ease-in-out duration-250"
|
||||
:label="t('settings.language.title')"
|
||||
:description="t('settings.language.description')"
|
||||
:options="languages"
|
||||
:options="languages as Array<{ value: string; label: string }>"
|
||||
/>
|
||||
|
||||
<!-- Controls Island Icon Size -->
|
||||
<FieldSelect
|
||||
v-if="showControlsIsland"
|
||||
v-model="settings.controlsIslandIconSize"
|
||||
v-motion
|
||||
:initial="{ opacity: 0, y: 10 }"
|
||||
:enter="{ opacity: 1, y: 0 }"
|
||||
:duration="250 + (4 * 10)"
|
||||
:delay="4 * 50"
|
||||
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') },
|
||||
]"
|
||||
/>
|
||||
|
||||
<div
|
||||
|
||||
@@ -115,6 +115,8 @@ export const useSettings = defineStore('settings', () => {
|
||||
|
||||
const [allowVisibleOnAllWorkspaces, resetAllowVisibleOnAllWorkspaces] = createResettableLocalStorage('settings/allow-visible-on-all-workspaces', true)
|
||||
|
||||
const [controlsIslandIconSize, resetControlsIslandIconSize] = createResettableLocalStorage<'auto' | 'large' | 'small'>('settings/controls-island/icon-size', 'auto')
|
||||
|
||||
function getLanguage() {
|
||||
let language = localStorage.getItem('settings/language')
|
||||
|
||||
@@ -186,6 +188,7 @@ export const useSettings = defineStore('settings', () => {
|
||||
resetThemeColorsHueDynamic()
|
||||
|
||||
resetAllowVisibleOnAllWorkspaces()
|
||||
resetControlsIslandIconSize()
|
||||
|
||||
await updateStageModel()
|
||||
}
|
||||
@@ -212,6 +215,7 @@ export const useSettings = defineStore('settings', () => {
|
||||
themeColorsHueDynamic,
|
||||
|
||||
allowVisibleOnAllWorkspaces,
|
||||
controlsIslandIconSize,
|
||||
|
||||
setThemeColorsHue,
|
||||
applyPrimaryColorFrom,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
ComboboxItem,
|
||||
ComboboxItemIndicator,
|
||||
ComboboxLabel,
|
||||
ComboboxPortal,
|
||||
ComboboxRoot,
|
||||
ComboboxSeparator,
|
||||
ComboboxTrigger,
|
||||
@@ -62,69 +63,76 @@ function toDisplayValue(value: T): string {
|
||||
</ComboboxTrigger>
|
||||
</ComboboxAnchor>
|
||||
|
||||
<ComboboxContent
|
||||
:avoid-collisions="true"
|
||||
:class="[
|
||||
'absolute z-10 w-full mt-1 min-w-[160px] overflow-hidden rounded-xl shadow-sm border will-change-[opacity,transform] max-h-50dvh',
|
||||
'data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade',
|
||||
'bg-white dark:bg-neutral-900',
|
||||
'border-neutral-200 dark:border-neutral-800 border-solid border-2 focus:border-neutral-300 dark:focus:border-neutral-600',
|
||||
]"
|
||||
>
|
||||
<ComboboxViewport class="p-[2px]">
|
||||
<ComboboxEmpty
|
||||
:class="[
|
||||
'font-medium py-2 px-2',
|
||||
'text-xs text-neutral-700 dark:text-neutral-200',
|
||||
'transition-colors duration-200 ease-in-out',
|
||||
]"
|
||||
/>
|
||||
<ComboboxPortal>
|
||||
<ComboboxContent
|
||||
position="popper"
|
||||
side="bottom"
|
||||
align="start"
|
||||
:side-offset="4"
|
||||
:avoid-collisions="true"
|
||||
:class="[
|
||||
'z-150 w-full min-w-[160px] overflow-hidden rounded-xl shadow-sm border will-change-[opacity,transform]',
|
||||
'data-[side=top]:animate-slideDownAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade data-[side=left]:animate-slideRightAndFade',
|
||||
'bg-white dark:bg-neutral-900',
|
||||
'border-neutral-200 dark:border-neutral-800 border-solid border-2 focus:border-neutral-300 dark:focus:border-neutral-600',
|
||||
]"
|
||||
:style="{ width: 'var(--reka-combobox-trigger-width)' }"
|
||||
>
|
||||
<ComboboxViewport :class="['p-[2px]', 'max-h-50dvh', 'overflow-y-auto']">
|
||||
<ComboboxEmpty
|
||||
:class="[
|
||||
'font-medium py-2 px-2',
|
||||
'text-xs text-neutral-700 dark:text-neutral-200',
|
||||
'transition-colors duration-200 ease-in-out',
|
||||
]"
|
||||
/>
|
||||
|
||||
<template
|
||||
v-for="(group, index) in options"
|
||||
:key="group.groupLabel"
|
||||
>
|
||||
<ComboboxGroup :class="['overflow-x-hidden']">
|
||||
<ComboboxSeparator
|
||||
v-if="index !== 0"
|
||||
:class="['m-[5px]', 'h-[1px]', 'bg-neutral-400']"
|
||||
/>
|
||||
<template
|
||||
v-for="(group, index) in options"
|
||||
:key="group.groupLabel"
|
||||
>
|
||||
<ComboboxGroup :class="['overflow-x-hidden']">
|
||||
<ComboboxSeparator
|
||||
v-if="index !== 0"
|
||||
:class="['m-[5px]', 'h-[1px]', 'bg-neutral-400']"
|
||||
/>
|
||||
|
||||
<ComboboxLabel
|
||||
:class="[
|
||||
'px-[25px] text-xs leading-[25px]',
|
||||
'text-neutral-500 dark:text-neutral-400',
|
||||
'transition-colors duration-200 ease-in-out',
|
||||
]"
|
||||
>
|
||||
{{ group.groupLabel }}
|
||||
</ComboboxLabel>
|
||||
|
||||
<ComboboxItem
|
||||
v-for="option in group.children"
|
||||
:key="option.label"
|
||||
:text-value="option.label"
|
||||
:value="option.value"
|
||||
:class="[
|
||||
'leading-none rounded-lg flex items-center h-8 pr-[0.5rem] pl-[1.5rem] relative select-none data-[disabled]:pointer-events-none data-[highlighted]:outline-none',
|
||||
'data-[highlighted]:bg-neutral-100 dark:data-[highlighted]:bg-neutral-800',
|
||||
'text-sm text-neutral-700 dark:text-neutral-200 data-[disabled]:text-neutral-400 dark:data-[disabled]:text-neutral-600 data-[highlighted]:text-grass1',
|
||||
'transition-colors duration-200 ease-in-out',
|
||||
'cursor-pointer',
|
||||
]"
|
||||
>
|
||||
<ComboboxItemIndicator
|
||||
:class="['absolute', 'left-0', 'w-[25px]', 'inline-flex', 'items-center', 'justify-center', 'opacity-30']"
|
||||
<ComboboxLabel
|
||||
:class="[
|
||||
'px-[25px] text-xs leading-[25px]',
|
||||
'text-neutral-500 dark:text-neutral-400',
|
||||
'transition-colors duration-200 ease-in-out',
|
||||
]"
|
||||
>
|
||||
<div i-solar:alt-arrow-right-outline />
|
||||
</ComboboxItemIndicator>
|
||||
<span :class="['line-clamp-1', 'overflow-hidden', 'text-ellipsis', 'whitespace-nowrap']">
|
||||
{{ option.label }}
|
||||
</span>
|
||||
</ComboboxItem>
|
||||
</ComboboxGroup>
|
||||
</template>
|
||||
</ComboboxViewport>
|
||||
</ComboboxContent>
|
||||
{{ group.groupLabel }}
|
||||
</ComboboxLabel>
|
||||
|
||||
<ComboboxItem
|
||||
v-for="option in group.children"
|
||||
:key="option.label"
|
||||
:text-value="option.label"
|
||||
:value="option.value"
|
||||
:class="[
|
||||
'leading-normal rounded-lg flex items-center h-8 pr-[0.5rem] pl-[1.5rem] relative select-none data-[disabled]:pointer-events-none data-[highlighted]:outline-none',
|
||||
'data-[highlighted]:bg-neutral-100 dark:data-[highlighted]:bg-neutral-800',
|
||||
'text-sm text-neutral-700 dark:text-neutral-200 data-[disabled]:text-neutral-400 dark:data-[disabled]:text-neutral-600 data-[highlighted]:text-grass1',
|
||||
'transition-colors duration-200 ease-in-out',
|
||||
'cursor-pointer',
|
||||
]"
|
||||
>
|
||||
<ComboboxItemIndicator
|
||||
:class="['absolute', 'left-0', 'w-[25px]', 'inline-flex', 'items-center', 'justify-center', 'opacity-30']"
|
||||
>
|
||||
<div i-solar:alt-arrow-right-outline />
|
||||
</ComboboxItemIndicator>
|
||||
<span :class="['line-clamp-1', 'overflow-hidden', 'text-ellipsis', 'whitespace-nowrap']">
|
||||
{{ option.label }}
|
||||
</span>
|
||||
</ComboboxItem>
|
||||
</ComboboxGroup>
|
||||
</template>
|
||||
</ComboboxViewport>
|
||||
</ComboboxContent>
|
||||
</ComboboxPortal>
|
||||
</ComboboxRoot>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user