fix(stage-*): qr should have dark mode adapted, connection component moved

This commit is contained in:
Neko Ayaka
2026-04-08 01:06:40 +08:00
parent 1326a3c31a
commit 160176b33a
12 changed files with 70 additions and 81 deletions
@@ -107,7 +107,7 @@ const isTransparent = computed(() => {
return true return true
}) })
const { isNearAnyBorder: isAroundWindowBorder } = useElectronMouseAroundWindowBorder({ threshold: 30 }) const { isNearAnyBorder: isAroundWindowBorder } = useElectronMouseAroundWindowBorder({ threshold: 10 })
const isAroundWindowBorderFor250Ms = refDebounced(isAroundWindowBorder, 250) const isAroundWindowBorderFor250Ms = refDebounced(isAroundWindowBorder, 250)
const setIgnoreMouseEvents = useElectronEventaInvoke(electron.window.setIgnoreMouseEvents) const setIgnoreMouseEvents = useElectronEventaInvoke(electron.window.setIgnoreMouseEvents)
@@ -1,7 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import ConnectionSettings from '@proj-airi/stage-pages/pages/settings/connection/ConnectionSettings.vue'
import { isStageTamagotchi } from '@proj-airi/stage-shared' import { isStageTamagotchi } from '@proj-airi/stage-shared'
import { ConnectionSettings } from '@proj-airi/stage-ui/components'
import { Callout, FieldCheckbox, FieldInput, SelectTab } from '@proj-airi/ui' import { Callout, FieldCheckbox, FieldInput, SelectTab } from '@proj-airi/ui'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { computed } from 'vue' import { computed } from 'vue'
@@ -53,80 +52,61 @@ const exposureModeOptions = computed(() => [
</script> </script>
<template> <template>
<ConnectionSettings> <div>
<template #platform-specific> <Callout
<!-- TODO: show connected remote --> v-if="lastApplyError"
<FieldCheckbox theme="orange"
v-model="websocketTlsEnabled" :label="t('settings.websocket-secure-enabled.title')"
v-motion >
:initial="{ opacity: 0, y: 10 }" {{ lastApplyError }}
:enter="{ opacity: 1, y: 0 }" </Callout>
:duration="250 + (5 * 10)" <ConnectionSettings>
:delay="5 * 50" <template #platform-specific>
:label="t('settings.websocket-secure-enabled.title')" <!-- TODO: show connected remote -->
:description="t('settings.websocket-secure-enabled.description')" <FieldCheckbox
/> v-model="websocketTlsEnabled"
:label="t('settings.websocket-secure-enabled.title')"
<div :description="t('settings.websocket-secure-enabled.description')"
v-if="showDesktopServerControls"
v-motion
:initial="{ opacity: 0, y: 10 }"
:enter="{ opacity: 1, y: 0 }"
:duration="250 + (6 * 10)"
:delay="6 * 50"
:class="['flex', 'flex-col', 'gap-2']"
>
<div :class="['text-sm', 'font-medium', 'text-neutral-900', 'dark:text-neutral-100']">
{{ t('settings.pages.connection.server-hostname.label') }}
</div>
<div :class="['text-xs', 'text-neutral-500', 'dark:text-neutral-400']">
{{ t('settings.pages.connection.server-hostname.description') }}
</div>
<SelectTab
v-model="exposureMode"
size="sm"
:options="exposureModeOptions"
/> />
</div>
<FieldInput <div
v-if="showDesktopServerControls && showAdvancedHostname" v-if="showDesktopServerControls"
v-model="hostname" :class="['flex', 'flex-col', 'gap-2']"
v-motion >
:initial="{ opacity: 0, y: 10 }" <div :class="['text-sm', 'font-medium', 'text-neutral-900', 'dark:text-neutral-100']">
:enter="{ opacity: 1, y: 0 }" {{ t('settings.pages.connection.server-hostname.label') }}
:duration="250 + (7 * 10)" </div>
:delay="7 * 50" <div :class="['text-xs', 'text-neutral-500', 'dark:text-neutral-400']">
:label="t('settings.pages.connection.server-hostname.advanced-label')" {{ t('settings.pages.connection.server-hostname.description') }}
:description="t('settings.pages.connection.server-hostname.advanced-description')" </div>
placeholder="192.168.1.25" <SelectTab
/> v-model="exposureMode"
size="sm"
:options="exposureModeOptions"
/>
</div>
<FieldInput <FieldInput
v-if="showDesktopServerControls" v-if="showDesktopServerControls && showAdvancedHostname"
v-model="authToken" v-model="hostname"
v-motion :label="t('settings.pages.connection.server-hostname.advanced-label')"
:initial="{ opacity: 0, y: 10 }" :description="t('settings.pages.connection.server-hostname.advanced-description')"
:enter="{ opacity: 1, y: 0 }" placeholder="192.168.1.25"
:duration="250 + (8 * 10)" />
:delay="8 * 50"
secure
:label="t('settings.pages.connection.server-auth-token.label')"
:description="t('settings.pages.connection.server-auth-token.description')"
:placeholder="t('settings.pages.connection.server-auth-token.placeholder')"
/>
<Callout <FieldInput
v-if="lastApplyError" v-if="showDesktopServerControls"
theme="orange" v-model="authToken"
:label="t('settings.websocket-secure-enabled.title')" type="password"
> :label="t('settings.pages.connection.server-auth-token.label')"
{{ lastApplyError }} :description="t('settings.pages.connection.server-auth-token.description')"
</Callout> :placeholder="t('settings.pages.connection.server-auth-token.placeholder')"
/>
<ServerChannelQrCard /> <ServerChannelQrCard />
</template> </template>
</ConnectionSettings> </ConnectionSettings>
</div>
</template> </template>
<route lang="yaml"> <route lang="yaml">
@@ -3,7 +3,7 @@ import type { ServerChannelQrPayload } from '@proj-airi/stage-shared/server-chan
import { errorMessageFrom } from '@moeru/std' import { errorMessageFrom } from '@moeru/std'
import { useElectronEventaInvoke } from '@proj-airi/electron-vueuse' import { useElectronEventaInvoke } from '@proj-airi/electron-vueuse'
import { Button, Callout, Collapsible } from '@proj-airi/ui' import { Button, Callout, Collapsible, useTheme } from '@proj-airi/ui'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { renderSVG } from 'uqr' import { renderSVG } from 'uqr'
import { computed, shallowRef, watch } from 'vue' import { computed, shallowRef, watch } from 'vue'
@@ -12,6 +12,7 @@ import { useI18n } from 'vue-i18n'
import { electronGetServerChannelQrPayload } from '../../../../shared/eventa' import { electronGetServerChannelQrPayload } from '../../../../shared/eventa'
import { useServerChannelSettingsStore } from '../../../stores/settings/server-channel' import { useServerChannelSettingsStore } from '../../../stores/settings/server-channel'
const { isDark } = useTheme()
const { t } = useI18n() const { t } = useI18n()
const getServerChannelQrPayload = useElectronEventaInvoke(electronGetServerChannelQrPayload) const getServerChannelQrPayload = useElectronEventaInvoke(electronGetServerChannelQrPayload)
const { authToken, hostname, tlsConfig } = storeToRefs(useServerChannelSettingsStore()) const { authToken, hostname, tlsConfig } = storeToRefs(useServerChannelSettingsStore())
@@ -38,7 +39,7 @@ const qrCodeSource = computed(() => {
ecc: 'M', ecc: 'M',
pixelSize: 8, pixelSize: 8,
whiteColor: 'transparent', whiteColor: 'transparent',
blackColor: '#111827', blackColor: isDark.value ? '#D5D5D5' : '#121212',
}) })
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}` return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`
@@ -94,7 +95,7 @@ watch([hostname, tlsConfig, authToken], () => {
<div <div
:class="[ :class="[
'mt-3 rounded-2xl border border-neutral-200/70 bg-white/70 p-4 dark:border-neutral-700/70 dark:bg-neutral-900/50', 'mt-3 rounded-xl bg-white/70 p-4 dark:bg-neutral-900/50',
'flex flex-col gap-4', 'flex flex-col gap-4',
]" ]"
> >
@@ -14,8 +14,8 @@ const { statusMessage, statusTone, handleStatus } = createDataSettingsStatusStat
<template> <template>
<div :class="['flex flex-col gap-4 pb-4']"> <div :class="['flex flex-col gap-4 pb-4']">
<DesktopFolderSection @status="handleStatus" />
<StatusBanner v-if="statusMessage" :message="statusMessage" :tone="statusTone" /> <StatusBanner v-if="statusMessage" :message="statusMessage" :tone="statusTone" />
<DesktopFolderSection @status="handleStatus" />
<ChatsSection @status="handleStatus" /> <ChatsSection @status="handleStatus" />
<ModelsModulesSection @status="handleStatus" /> <ModelsModulesSection @status="handleStatus" />
<DesktopResetSection @status="handleStatus" /> <DesktopResetSection @status="handleStatus" />
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import ConnectionSettings from './ConnectionSettings.vue' import { ConnectionSettings } from '@proj-airi/stage-ui/components'
</script> </script>
<template> <template>
@@ -12,7 +12,7 @@ defineProps<Props>()
<template> <template>
<div <div
:class="[ :class="[
'rounded-xl border px-4 py-3 text-sm shadow-sm', 'rounded-xl border-2 px-4 py-3 text-sm shadow-sm',
tone === 'success' && 'border-emerald-300/80 bg-emerald-50/80 text-emerald-700 dark:border-emerald-500/40 dark:bg-emerald-500/10 dark:text-emerald-200', tone === 'success' && 'border-emerald-300/80 bg-emerald-50/80 text-emerald-700 dark:border-emerald-500/40 dark:bg-emerald-500/10 dark:text-emerald-200',
tone === 'error' && 'border-red-300/80 bg-red-50/80 text-red-700 dark:border-red-500/50 dark:bg-red-500/10 dark:text-red-200', tone === 'error' && 'border-red-300/80 bg-red-50/80 text-red-700 dark:border-red-500/50 dark:bg-red-500/10 dark:text-red-200',
tone === 'neutral' && 'border-neutral-200/70 bg-white/70 text-neutral-700 dark:border-neutral-700/70 dark:bg-neutral-900/60 dark:text-neutral-200', tone === 'neutral' && 'border-neutral-200/70 bg-white/70 text-neutral-700 dark:border-neutral-700/70 dark:bg-neutral-900/60 dark:text-neutral-200',
@@ -0,0 +1 @@
export * from './settings'
@@ -0,0 +1 @@
export { default as ConnectionSettings } from './index.vue'
@@ -1,10 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { useModsServerChannelStore } from '@proj-airi/stage-ui/stores/mods/api/channel-server'
import { FieldInput } from '@proj-airi/ui' import { FieldInput } from '@proj-airi/ui'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { computed } from 'vue' import { computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useModsServerChannelStore } from '../../../../stores/mods/api/channel-server'
const { t } = useI18n() const { t } = useI18n()
const { websocketUrl } = storeToRefs(useModsServerChannelStore()) const { websocketUrl } = storeToRefs(useModsServerChannelStore())
@@ -1,5 +1,6 @@
export * from './about' export * from './about'
export * from './chat' export * from './chat'
export * from './connection'
export * from './dialogs' export * from './dialogs'
export * from './hologram' export * from './hologram'
export * from './providers' export * from './providers'
@@ -1,8 +1,10 @@
<script <script
setup setup
lang="ts" lang="ts"
generic="InputType extends 'number' | string, T = InputType extends 'number' ? (number | undefined) : ((string | undefined))" generic="InputType extends 'number' | InputTypeHTMLAttribute | string, T = InputType extends 'number' ? (number | undefined) : ((string | undefined))"
> >
import type { InputTypeHTMLAttribute } from 'vue'
import { Input } from '../input' import { Input } from '../input'
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
@@ -1,8 +1,10 @@
<script <script
setup setup
lang="ts" lang="ts"
generic="InputType extends 'number' | string, T = InputType extends 'number' ? (number | undefined) : ((string | undefined))" generic="InputType extends 'number' | InputTypeHTMLAttribute | string, T = InputType extends 'number' ? (number | undefined) : ((string | undefined))"
> >
import type { InputTypeHTMLAttribute } from 'vue'
// Define button variants for better type safety and maintainability // Define button variants for better type safety and maintainability
type InputVariant = 'primary' | 'secondary' | 'primary-dimmed' type InputVariant = 'primary' | 'secondary' | 'primary-dimmed'