diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-02-27 09:32:39 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-27 00:32:39 +0000 |
| commit | ec8381522789a7fc4329a627770d80a0cd7774a4 (patch) | |
| tree | a4bf4db4bae51fa4020def0fea19b08154eaf563 /packages/frontend/src | |
| parent | Update CHANGELOG.md (diff) | |
| download | sharkey-ec8381522789a7fc4329a627770d80a0cd7774a4.tar.gz sharkey-ec8381522789a7fc4329a627770d80a0cd7774a4.tar.bz2 sharkey-ec8381522789a7fc4329a627770d80a0cd7774a4.zip | |
fix(frontend): MkSelectの初期値が表示されない場合がある (#15559)
* fix
* Update CHANGELOG.md
Diffstat (limited to 'packages/frontend/src')
| -rw-r--r-- | packages/frontend/src/components/MkSelect.vue | 130 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/theme.vue | 83 |
2 files changed, 167 insertions, 46 deletions
diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue index 83a79d0c9f..15717802ec 100644 --- a/packages/frontend/src/components/MkSelect.vue +++ b/packages/frontend/src/components/MkSelect.vue @@ -41,11 +41,28 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, nextTick, ref, watch, computed, toRefs, useSlots } from 'vue'; -import type { VNode, VNodeChild } from 'vue'; import { useInterval } from '@@/js/use-interval.js'; +import type { VNode, VNodeChild } from 'vue'; import type { MenuItem } from '@/types/menu.js'; import * as os from '@/os.js'; +type ItemOption = { + type?: 'option'; + value: string | number | null; + label: string; +}; + +type ItemGroup = { + type: 'group'; + label: string; + items: ItemOption[]; +}; + +export type MkSelectItem = ItemOption | ItemGroup; + +// TODO: itemsをslot内のoptionで指定する用法は廃止する(props.itemsを必須化する) +// see: https://github.com/misskey-dev/misskey/issues/15558 + const props = defineProps<{ modelValue: string | number | null; required?: boolean; @@ -56,6 +73,7 @@ const props = defineProps<{ inline?: boolean; small?: boolean; large?: boolean; + items?: MkSelectItem[]; }>(); const emit = defineEmits<{ @@ -107,7 +125,30 @@ onMounted(() => { }); }); -watch(modelValue, () => { +watch([modelValue, () => props.items], () => { + if (props.items) { + let found: ItemOption | null = null; + for (const item of props.items) { + if (item.type === 'group') { + for (const option of item.items) { + if (option.value === modelValue.value) { + found = option; + break; + } + } + } else { + if (item.value === modelValue.value) { + found = item; + break; + } + } + } + if (found) { + currentValueText.value = found.label; + } + return; + } + const scanOptions = (options: VNodeChild[]) => { for (const vnode of options) { if (typeof vnode !== 'object' || vnode === null || Array.isArray(vnode)) continue; @@ -130,7 +171,7 @@ watch(modelValue, () => { }; scanOptions(slots.default!()); -}, { immediate: true }); +}, { immediate: true, deep: true }); function show() { if (opening.value) return; @@ -139,41 +180,70 @@ function show() { opening.value = true; const menu: MenuItem[] = []; - let options = slots.default!(); - const pushOption = (option: VNode) => { - menu.push({ - text: option.children as string, - active: computed(() => modelValue.value === option.props?.value), - action: () => { - emit('update:modelValue', option.props?.value); - }, - }); - }; - - const scanOptions = (options: VNodeChild[]) => { - for (const vnode of options) { - if (typeof vnode !== 'object' || vnode === null || Array.isArray(vnode)) continue; - if (vnode.type === 'optgroup') { - const optgroup = vnode; + if (props.items) { + for (const item of props.items) { + if (item.type === 'group') { menu.push({ type: 'label', - text: optgroup.props?.label, + text: item.label, }); - if (Array.isArray(optgroup.children)) scanOptions(optgroup.children); - } else if (Array.isArray(vnode.children)) { // 何故かフラグメントになってくることがある - const fragment = vnode; - if (Array.isArray(fragment.children)) scanOptions(fragment.children); - } else if (vnode.props == null) { // v-if で条件が false のときにこうなる - // nop? + for (const option of item.items) { + menu.push({ + text: option.label, + active: computed(() => modelValue.value === option.value), + action: () => { + emit('update:modelValue', option.value); + }, + }); + } } else { - const option = vnode; - pushOption(option); + menu.push({ + text: item.label, + active: computed(() => modelValue.value === item.value), + action: () => { + emit('update:modelValue', item.value); + }, + }); } } - }; + } else { + let options = slots.default!(); + + const pushOption = (option: VNode) => { + menu.push({ + text: option.children as string, + active: computed(() => modelValue.value === option.props?.value), + action: () => { + emit('update:modelValue', option.props?.value); + }, + }); + }; - scanOptions(options); + const scanOptions = (options: VNodeChild[]) => { + for (const vnode of options) { + if (typeof vnode !== 'object' || vnode === null || Array.isArray(vnode)) continue; + if (vnode.type === 'optgroup') { + const optgroup = vnode; + menu.push({ + type: 'label', + text: optgroup.props?.label, + }); + if (Array.isArray(optgroup.children)) scanOptions(optgroup.children); + } else if (Array.isArray(vnode.children)) { // 何故かフラグメントになってくることがある + const fragment = vnode; + if (Array.isArray(fragment.children)) scanOptions(fragment.children); + } else if (vnode.props == null) { // v-if で条件が false のときにこうなる + // nop? + } else { + const option = vnode; + pushOption(option); + } + } + }; + + scanOptions(options); + } os.popupMenu(menu, container.value, { width: container.value?.offsetWidth, diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue index f1ec231588..fcf5b3cd9b 100644 --- a/packages/frontend/src/pages/settings/theme.vue +++ b/packages/frontend/src/pages/settings/theme.vue @@ -32,27 +32,13 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div class="selects"> - <MkSelect v-model="lightThemeId" large class="select"> + <MkSelect v-model="lightThemeId" large class="select" :items="lightThemeSelectorItems"> <template #label>{{ i18n.ts.themeForLightMode }}</template> <template #prefix><i class="ti ti-sun"></i></template> - <option v-if="instanceLightTheme" :key="'instance:' + instanceLightTheme.id" :value="instanceLightTheme.id">{{ instanceLightTheme.name }}</option> - <optgroup v-if="installedLightThemes.length > 0" :label="i18n.ts._theme.installedThemes"> - <option v-for="x in installedLightThemes" :key="'installed:' + x.id" :value="x.id">{{ x.name }}</option> - </optgroup> - <optgroup :label="i18n.ts._theme.builtinThemes"> - <option v-for="x in builtinLightThemes" :key="'builtin:' + x.id" :value="x.id">{{ x.name }}</option> - </optgroup> </MkSelect> - <MkSelect v-model="darkThemeId" large class="select"> + <MkSelect v-model="darkThemeId" large class="select" :items="darkThemeSelectorItems"> <template #label>{{ i18n.ts.themeForDarkMode }}</template> <template #prefix><i class="ti ti-moon"></i></template> - <option v-if="instanceDarkTheme" :key="'instance:' + instanceDarkTheme.id" :value="instanceDarkTheme.id">{{ instanceDarkTheme.name }}</option> - <optgroup v-if="installedDarkThemes.length > 0" :label="i18n.ts._theme.installedThemes"> - <option v-for="x in installedDarkThemes" :key="'installed:' + x.id" :value="x.id">{{ x.name }}</option> - </optgroup> - <optgroup :label="i18n.ts._theme.builtinThemes"> - <option v-for="x in builtinDarkThemes" :key="'builtin:' + x.id" :value="x.id">{{ x.name }}</option> - </optgroup> </MkSelect> </div> @@ -73,6 +59,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, onActivated, ref, watch } from 'vue'; import JSON5 from 'json5'; +import type { MkSelectItem } from '@/components/MkSelect.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkSelect from '@/components/MkSelect.vue'; import FormSection from '@/components/form/section.vue'; @@ -102,6 +89,70 @@ const installedLightThemes = computed(() => installedThemes.value.filter(t => t. const builtinLightThemes = computed(() => builtinThemes.value.filter(t => t.base === 'light' || t.kind === 'light')); const themes = computed(() => uniqueBy([instanceDarkTheme.value, instanceLightTheme.value, ...builtinThemes.value, ...installedThemes.value].filter(x => x != null), theme => theme.id)); +const lightThemeSelectorItems = computed(() => { + const items = [] as MkSelectItem[]; + if (instanceLightTheme.value) { + items.push({ + type: 'option', + value: instanceLightTheme.value.id, + label: instanceLightTheme.value.name, + }); + } + if (installedLightThemes.value.length > 0) { + items.push({ + type: 'group', + label: i18n.ts._theme.installedThemes, + items: installedLightThemes.value.map(x => ({ + type: 'option', + value: x.id, + label: x.name, + })), + }); + } + items.push({ + type: 'group', + label: i18n.ts._theme.builtinThemes, + items: builtinLightThemes.value.map(x => ({ + type: 'option', + value: x.id, + label: x.name, + })), + }); + return items; +}); + +const darkThemeSelectorItems = computed(() => { + const items = [] as MkSelectItem[]; + if (instanceDarkTheme.value) { + items.push({ + type: 'option', + value: instanceDarkTheme.value.id, + label: instanceDarkTheme.value.name, + }); + } + if (installedDarkThemes.value.length > 0) { + items.push({ + type: 'group', + label: i18n.ts._theme.installedThemes, + items: installedDarkThemes.value.map(x => ({ + type: 'option', + value: x.id, + label: x.name, + })), + }); + } + items.push({ + type: 'group', + label: i18n.ts._theme.builtinThemes, + items: builtinDarkThemes.value.map(x => ({ + type: 'option', + value: x.id, + label: x.name, + })), + }); + return items; +}); + const darkTheme = ColdDeviceStorage.ref('darkTheme'); const darkThemeId = computed({ get() { |