Help Modal

This commit is contained in:
th committed 2026-04-23 19:33:22 +02:00
1 parent 8e8aa5f27c
commit 8d987d1526
11 files changed
+2405 -58

No files matched your search

+122 -43
View File
@@ -5,7 +5,6 @@ import {
PhFolder,
PhFrameCorners,
PhMoon,
PhPalette,
PhSlidersHorizontal,
PhSun,
PhX,
@@ -18,7 +17,8 @@ import { useTooltipPref } from '@/composables/useTooltipPref'
import { useAssetThumbsPref } from '@/composables/useAssetThumbsPref'
import { showLocked } from '@/composables/useShowLocked'
import { Modal, confirmDialog, promptDialog } from '@/components/ui'
import { t } from '@/i18n'
import { setLocale, t, useI18n } from '@/i18n'
import type { Locale } from '@/i18n'
const props = defineProps<{ modelValue: boolean }>()
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
@@ -28,6 +28,7 @@ const clipart = useClipartStore()
const { theme, setTheme } = useTheme()
const { enabled: tooltipsEnabled } = useTooltipPref()
const { enabled: thumbsEnabled } = useAssetThumbsPref()
const { locale } = useI18n()
// Re-check the stored clipart folder whenever settings opens — covers the
// case where initial App.vue init raced ahead of IDB or the user opens
@@ -39,13 +40,12 @@ watch(
},
)
type TabId = 'document' | 'clipart' | 'interface' | 'theme'
type TabId = 'document' | 'clipart' | 'interface'
const tabs: { id: TabId; label: string; icon: typeof PhFrameCorners }[] = [
{ id: 'document', label: t('settings.tab.document'), icon: PhFrameCorners },
{ id: 'clipart', label: t('settings.tab.clipart'), icon: PhFolder },
{ id: 'interface', label: t('settings.tab.interface'), icon: PhSlidersHorizontal },
{ id: 'theme', label: t('settings.tab.theme'), icon: PhPalette },
const tabs: { id: TabId; labelKey: string; icon: typeof PhFrameCorners }[] = [
{ id: 'document', labelKey: 'settings.tab.document', icon: PhFrameCorners },
{ id: 'clipart', labelKey: 'settings.tab.clipart', icon: PhFolder },
{ id: 'interface', labelKey: 'settings.tab.interface', icon: PhSlidersHorizontal },
]
const activeTab = ref<TabId>('document')
@@ -195,6 +195,11 @@ const fps = computed({
set: (v: number) => store.setFps(v),
})
const selectedLocale = computed({
get: () => locale.value,
set: (value: Locale) => setLocale(value),
})
const presets: { label: string; w: number; h: number }[] = [
{ label: '16:9 · 1280×720', w: 1280, h: 720 },
{ label: '16:9 · 1920×1080', w: 1920, h: 1080 },
@@ -221,6 +226,15 @@ function applyTheme(t: Theme) {
:model-value="modelValue"
:title="t('settings.title')"
size="lg"
:dialog-style="{
width: 'min(920px, calc(100% - 32px))',
height: 'min(620px, calc(100vh - 48px))',
}"
:body-style="{
display: 'flex',
overflow: 'hidden',
minHeight: 0,
}"
@update:model-value="emit('update:modelValue', $event)"
>
<div class="body">
@@ -233,7 +247,7 @@ function applyTheme(t: Theme) {
@click="activeTab = tab.id"
>
<component :is="tab.icon" :size="14" />
{{ tab.label }}
{{ t(tab.labelKey) }}
</button>
</nav>
@@ -362,6 +376,18 @@ function applyTheme(t: Theme) {
<section v-else-if="activeTab === 'interface'">
<h3>{{ t('settings.tab.interface') }}</h3>
<div class="setting-block">
<div class="setting-heading">{{ t('settings.languageTitle') }}</div>
<div class="setting-desc">{{ t('settings.languageDesc') }}</div>
<label class="setting-select">
<span class="setting-label">{{ t('settings.languageLabel') }}</span>
<select v-model="selectedLocale">
<option value="en">{{ t('settings.languageEnglish') }}</option>
<option value="de">{{ t('settings.languageGerman') }}</option>
</select>
</label>
</div>
<label class="row-toggle">
<span class="switch" :class="{ on: tooltipsEnabled }">
<input type="checkbox" v-model="tooltipsEnabled" />
@@ -391,41 +417,41 @@ function applyTheme(t: Theme) {
<div class="row-toggle-desc">{{ t('settings.assetThumbsDesc') }}</div>
</div>
</label>
</section>
<section v-else-if="activeTab === 'theme'">
<h3>{{ t('settings.tab.theme') }}</h3>
<div class="theme-grid">
<button
class="theme-card"
:class="{ active: theme === 'dark' }"
@click="applyTheme('dark')"
>
<div class="theme-preview theme-preview-dark">
<div class="tp-panel"></div>
<div class="tp-accent"></div>
</div>
<div class="theme-label">
<PhMoon :size="14" />
<span>{{ t('settings.themeDark') }}</span>
</div>
</button>
<button
class="theme-card"
:class="{ active: theme === 'light' }"
@click="applyTheme('light')"
>
<div class="theme-preview theme-preview-light">
<div class="tp-panel"></div>
<div class="tp-accent"></div>
</div>
<div class="theme-label">
<PhSun :size="14" />
<span>{{ t('settings.themeLight') }}</span>
</div>
</button>
<div class="setting-block">
<div class="setting-heading">{{ t('settings.appearanceTitle') }}</div>
<div class="setting-desc">{{ t('settings.themeHint') }}</div>
<div class="theme-grid">
<button
class="theme-card"
:class="{ active: theme === 'dark' }"
@click="applyTheme('dark')"
>
<div class="theme-preview theme-preview-dark">
<div class="tp-panel"></div>
<div class="tp-accent"></div>
</div>
<div class="theme-label">
<PhMoon :size="14" />
<span>{{ t('settings.themeDark') }}</span>
</div>
</button>
<button
class="theme-card"
:class="{ active: theme === 'light' }"
@click="applyTheme('light')"
>
<div class="theme-preview theme-preview-light">
<div class="tp-panel"></div>
<div class="tp-accent"></div>
</div>
<div class="theme-label">
<PhSun :size="14" />
<span>{{ t('settings.themeLight') }}</span>
</div>
</button>
</div>
</div>
<p class="hint">{{ t('settings.themeHint') }}</p>
</section>
</div>
</div>
@@ -438,7 +464,8 @@ function applyTheme(t: Theme) {
<style scoped>
.body {
display: flex;
min-height: 340px;
flex: 1 1 auto;
min-height: 0;
}
.tabs {
@@ -450,6 +477,7 @@ function applyTheme(t: Theme) {
flex-direction: column;
gap: 2px;
background: var(--panel-alt);
min-height: 0;
}
.tab {
@@ -478,6 +506,7 @@ function applyTheme(t: Theme) {
padding: 16px 20px;
overflow: auto;
min-width: 0;
min-height: 0;
}
section {
@@ -605,6 +634,56 @@ input[type='number'] {
margin: 0;
}
.setting-block {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--panel-alt);
}
.setting-heading {
font-size: 13px;
font-weight: 600;
color: var(--text);
}
.setting-desc {
font-size: 12px;
line-height: 1.45;
color: var(--text-dim);
}
.setting-select {
display: flex;
flex-direction: column;
gap: 6px;
flex: 0 0 auto;
font-size: 11px;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.setting-label {
color: var(--text-dim);
}
.setting-select select {
width: 100%;
background: var(--bg);
border: 1px solid var(--border);
color: var(--text);
padding: 8px 10px;
border-radius: var(--radius-sm);
font: inherit;
font-size: 13px;
text-transform: none;
letter-spacing: normal;
}
/* Switch + two-line label. Defensive resets defeat the scoped
blanket `label { flex-direction: column; text-transform: uppercase;
font-size: 10px; flex: 1 1 0; }` that form-row labels in this file