fix os.select

This commit is contained in:
kakkokari-gtyih 2025-08-29 12:07:25 +09:00
parent f1c1410834
commit 496cbb02ff
21 changed files with 85 additions and 118 deletions

View File

@ -47,7 +47,7 @@ import MkModal from '@/components/MkModal.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue'; import MkSelect from '@/components/MkSelect.vue';
import type { MkSelectItem } from '@/components/MkSelect.vue'; import type { MkSelectItem, OptionValue } from '@/components/MkSelect.vue';
import { useMkSelect } from '@/composables/use-mkselect.js'; import { useMkSelect } from '@/composables/use-mkselect.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
@ -62,7 +62,7 @@ type Input = {
type Select = { type Select = {
items: MkSelectItem[]; items: MkSelectItem[];
default: string | null; default: OptionValue | null;
}; };
type Result = string | number | true | null; type Result = string | number | true | null;

View File

@ -567,11 +567,11 @@ async function toggleReactionAcceptance() {
const select = await os.select({ const select = await os.select({
title: i18n.ts.reactionAcceptance, title: i18n.ts.reactionAcceptance,
items: [ items: [
{ value: null, text: i18n.ts.all }, { value: null, label: i18n.ts.all },
{ value: 'likeOnlyForRemote' as const, text: i18n.ts.likeOnlyForRemote }, { value: 'likeOnlyForRemote' as const, label: i18n.ts.likeOnlyForRemote },
{ value: 'nonSensitiveOnly' as const, text: i18n.ts.nonSensitiveOnly }, { value: 'nonSensitiveOnly' as const, label: i18n.ts.nonSensitiveOnly },
{ value: 'nonSensitiveOnlyForLocalLikeOnlyForRemote' as const, text: i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote }, { value: 'nonSensitiveOnlyForLocalLikeOnlyForRemote' as const, label: i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote },
{ value: 'likeOnly' as const, text: i18n.ts.likeOnly }, { value: 'likeOnly' as const, label: i18n.ts.likeOnly },
], ],
default: reactionAcceptance.value, default: reactionAcceptance.value,
}); });

View File

@ -102,12 +102,12 @@ async function addRole() {
const items = roles.value const items = roles.value
.filter(r => r.isPublic) .filter(r => r.isPublic)
.filter(r => !selectedRoleIds.value.includes(r.id)) .filter(r => !selectedRoleIds.value.includes(r.id))
.map(r => ({ text: r.name, value: r })); .map(r => ({ label: r.name, value: r.id }));
const { canceled, result: role } = await os.select({ items }); const { canceled, result: roleId } = await os.select({ items });
if (canceled || role == null) return; if (canceled || roleId == null) return;
selectedRoleIds.value.push(role.id); selectedRoleIds.value.push(roleId);
} }
async function removeRole(roleId: string) { async function removeRole(roleId: string) {

View File

@ -14,6 +14,7 @@ import type { Form, GetFormResultType } from '@/utility/form.js';
import type { MenuItem } from '@/types/menu.js'; import type { MenuItem } from '@/types/menu.js';
import type { PostFormProps } from '@/types/post-form.js'; import type { PostFormProps } from '@/types/post-form.js';
import type { UploaderFeatures } from '@/composables/use-uploader.js'; import type { UploaderFeatures } from '@/composables/use-uploader.js';
import type { MkSelectItem, OptionValue } from '@/components/MkSelect.vue';
import type MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue'; import type MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue';
import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue'; import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
@ -502,50 +503,15 @@ export function authenticateDialog(): Promise<{
}); });
} }
type SelectItem<C> = { export function select<C extends OptionValue, D extends C | null = null>(props: {
value: C;
text: string;
};
// default が指定されていたら result は null になり得ないことを保証する overload function
export function select<C = unknown>(props: {
title?: string; title?: string;
text?: string; text?: string;
default: string; default?: D;
items: (SelectItem<C> | { items: (MkSelectItem<C> | undefined)[];
sectionTitle: string;
items: SelectItem<C>[];
} | undefined)[];
}): Promise<{ }): Promise<{
canceled: true; result: undefined; canceled: true; result: undefined;
} | { } | {
canceled: false; result: C; canceled: false; result: Exclude<D, undefined> extends null ? C | null : C;
}>;
export function select<C = unknown>(props: {
title?: string;
text?: string;
default?: string | null;
items: (SelectItem<C> | {
sectionTitle: string;
items: SelectItem<C>[];
} | undefined)[];
}): Promise<{
canceled: true; result: undefined;
} | {
canceled: false; result: C | null;
}>;
export function select<C = unknown>(props: {
title?: string;
text?: string;
default?: string | null;
items: (SelectItem<C> | {
sectionTitle: string;
items: SelectItem<C>[];
} | undefined)[];
}): Promise<{
canceled: true; result: undefined;
} | {
canceled: false; result: C | null;
}> { }> {
return new Promise(resolve => { return new Promise(resolve => {
const { dispose } = popup(MkDialog, { const { dispose } = popup(MkDialog, {

View File

@ -442,22 +442,22 @@ async function assignRole() {
const { canceled, result: roleId } = await os.select({ const { canceled, result: roleId } = await os.select({
title: i18n.ts._role.chooseRoleToAssign, title: i18n.ts._role.chooseRoleToAssign,
items: roles.map(r => ({ text: r.name, value: r.id })), items: roles.map(r => ({ label: r.name, value: r.id })),
}); });
if (canceled) return; if (canceled || roleId == null) return;
const { canceled: canceled2, result: period } = await os.select({ const { canceled: canceled2, result: period } = await os.select({
title: i18n.ts.period + ': ' + roles.find(r => r.id === roleId)!.name, title: i18n.ts.period + ': ' + roles.find(r => r.id === roleId)!.name,
items: [{ items: [{
value: 'indefinitely', text: i18n.ts.indefinitely, value: 'indefinitely', label: i18n.ts.indefinitely,
}, { }, {
value: 'oneHour', text: i18n.ts.oneHour, value: 'oneHour', label: i18n.ts.oneHour,
}, { }, {
value: 'oneDay', text: i18n.ts.oneDay, value: 'oneDay', label: i18n.ts.oneDay,
}, { }, {
value: 'oneWeek', text: i18n.ts.oneWeek, value: 'oneWeek', label: i18n.ts.oneWeek,
}, { }, {
value: 'oneMonth', text: i18n.ts.oneMonth, value: 'oneMonth', label: i18n.ts.oneMonth,
}], }],
default: 'indefinitely', default: 'indefinitely',
}); });

View File

@ -115,15 +115,15 @@ async function assign() {
const { canceled: canceled2, result: period } = await os.select({ const { canceled: canceled2, result: period } = await os.select({
title: i18n.ts.period + ': ' + role.name, title: i18n.ts.period + ': ' + role.name,
items: [{ items: [{
value: 'indefinitely', text: i18n.ts.indefinitely, value: 'indefinitely', label: i18n.ts.indefinitely,
}, { }, {
value: 'oneHour', text: i18n.ts.oneHour, value: 'oneHour', label: i18n.ts.oneHour,
}, { }, {
value: 'oneDay', text: i18n.ts.oneDay, value: 'oneDay', label: i18n.ts.oneDay,
}, { }, {
value: 'oneWeek', text: i18n.ts.oneWeek, value: 'oneWeek', label: i18n.ts.oneWeek,
}, { }, {
value: 'oneMonth', text: i18n.ts.oneMonth, value: 'oneMonth', label: i18n.ts.oneMonth,
}], }],
default: 'indefinitely', default: 'indefinitely',
}); });

View File

@ -101,12 +101,12 @@ async function addRole() {
const roles = await misskeyApi('admin/roles/list'); const roles = await misskeyApi('admin/roles/list');
const currentRoleIds = rolesThatCanBeUsedThisDecoration.value.map(x => x.id); const currentRoleIds = rolesThatCanBeUsedThisDecoration.value.map(x => x.id);
const { canceled, result: role } = await os.select({ const { canceled, result: roleId } = await os.select({
items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ text: r.name, value: r })), items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ label: r.name, value: r.id })),
}); });
if (canceled || role == null) return; if (canceled || roleId == null) return;
rolesThatCanBeUsedThisDecoration.value.push(role); rolesThatCanBeUsedThisDecoration.value.push(roles.find(r => r.id === roleId)!);
} }
async function removeRole(role, ev) { async function removeRole(role, ev) {

View File

@ -135,12 +135,12 @@ async function addRole() {
const roles = await misskeyApi('admin/roles/list'); const roles = await misskeyApi('admin/roles/list');
const currentRoleIds = rolesThatCanBeUsedThisEmojiAsReaction.value.map(x => x.id); const currentRoleIds = rolesThatCanBeUsedThisEmojiAsReaction.value.map(x => x.id);
const { canceled, result: role } = await os.select({ const { canceled, result: roleId } = await os.select({
items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ text: r.name, value: r })), items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ label: r.name, value: r.id })),
}); });
if (canceled || role == null) return; if (canceled || roleId == null) return;
rolesThatCanBeUsedThisEmojiAsReaction.value.push(role); rolesThatCanBeUsedThisEmojiAsReaction.value.push(roles.find(r => r.id === roleId)!);
} }
async function removeRole(role: Misskey.entities.RoleLite, ev: Event) { async function removeRole(role: Misskey.entities.RoleLite, ev: Event) {

View File

@ -4,12 +4,13 @@
*/ */
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import type { MkSelectItem } from '@/components/MkSelect.vue';
export function getPageBlockList() { export function getPageBlockList() {
return [ return [
{ value: 'section', text: i18n.ts._pages.blocks.section }, { value: 'section', label: i18n.ts._pages.blocks.section },
{ value: 'text', text: i18n.ts._pages.blocks.text }, { value: 'text', label: i18n.ts._pages.blocks.text },
{ value: 'image', text: i18n.ts._pages.blocks.image }, { value: 'image', label: i18n.ts._pages.blocks.image },
{ value: 'note', text: i18n.ts._pages.blocks.note }, { value: 'note', label: i18n.ts._pages.blocks.note },
]; ] as const satisfies MkSelectItem[];
} }

View File

@ -71,7 +71,7 @@ async function add() {
title: i18n.ts._pages.chooseBlock, title: i18n.ts._pages.chooseBlock,
items: getPageBlockList(), items: getPageBlockList(),
}); });
if (canceled) return; if (canceled || type == null) return;
const id = genId(); const id = genId();
children.value.push({ id, type }); children.value.push({ id, type });

View File

@ -210,11 +210,10 @@ async function duplicate() {
async function add() { async function add() {
const { canceled, result: type } = await os.select({ const { canceled, result: type } = await os.select({
type: null,
title: i18n.ts._pages.chooseBlock, title: i18n.ts._pages.chooseBlock,
items: getPageBlockList(), items: getPageBlockList(),
}); });
if (canceled) return; if (canceled || type == null) return;
const id = genId(); const id = genId();
content.value.push({ id, type }); content.value.push({ id, type });

View File

@ -86,9 +86,9 @@ async function addItem() {
const { canceled, result: item } = await os.select({ const { canceled, result: item } = await os.select({
title: i18n.ts.addItem, title: i18n.ts.addItem,
items: [...menu.map(k => ({ items: [...menu.map(k => ({
value: k, text: navbarItemDef[k].title, value: k, label: navbarItemDef[k].title,
})), { })), {
value: '-', text: i18n.ts.divider, value: '-', label: i18n.ts.divider,
}], }],
}); });
if (canceled || item == null) return; if (canceled || item == null) return;

View File

@ -1010,16 +1010,15 @@ function removeEmojiIndex(lang: string) {
async function setPinnedList() { async function setPinnedList() {
const lists = await misskeyApi('users/lists/list'); const lists = await misskeyApi('users/lists/list');
const { canceled, result: list } = await os.select({ const { canceled, result: listId } = await os.select({
title: i18n.ts.selectList, title: i18n.ts.selectList,
items: lists.map(x => ({ items: lists.map(x => ({
value: x, text: x.name, value: x.id, label: x.name,
})), })),
}); });
if (canceled) return; if (canceled || listId == null) return;
if (list == null) return;
prefer.commit('pinnedUserLists', [list]); prefer.commit('pinnedUserLists', [lists.find((x) => x.id === listId)!]);
} }
function removePinnedList() { function removePinnedList() {

View File

@ -447,16 +447,16 @@ export class PreferencesManager {
title: i18n.ts.preferenceSyncConflictTitle, title: i18n.ts.preferenceSyncConflictTitle,
text: i18n.ts.preferenceSyncConflictText, text: i18n.ts.preferenceSyncConflictText,
items: [...(mergedValue !== undefined ? [{ items: [...(mergedValue !== undefined ? [{
text: i18n.ts.preferenceSyncConflictChoiceMerge, label: i18n.ts.preferenceSyncConflictChoiceMerge,
value: 'merge', value: 'merge' as const,
}] : []), { }] : []), {
text: i18n.ts.preferenceSyncConflictChoiceServer, label: i18n.ts.preferenceSyncConflictChoiceServer,
value: 'remote', value: 'remote' as const,
}, { }, {
text: i18n.ts.preferenceSyncConflictChoiceDevice, label: i18n.ts.preferenceSyncConflictChoiceDevice,
value: 'local', value: 'local' as const,
}, { }, {
text: i18n.ts.preferenceSyncConflictChoiceCancel, label: i18n.ts.preferenceSyncConflictChoiceCancel,
value: null, value: null,
}], }],
default: mergedValue !== undefined ? 'merge' : 'remote', default: mergedValue !== undefined ? 'merge' : 'remote',

View File

@ -187,7 +187,7 @@ export async function restoreFromCloudBackup() {
const select = await os.select({ const select = await os.select({
title: i18n.ts._preferencesBackup.selectBackupToRestore, title: i18n.ts._preferencesBackup.selectBackupToRestore,
items: backups.map(backup => ({ items: backups.map(backup => ({
text: backup.name, label: backup.name,
value: backup.name, value: backup.name,
})), })),
}); });

View File

@ -168,7 +168,7 @@ const addColumn = async (ev) => {
const { canceled, result: column } = await os.select({ const { canceled, result: column } = await os.select({
title: i18n.ts._deck.addColumn, title: i18n.ts._deck.addColumn,
items: columnTypes.map(column => ({ items: columnTypes.map(column => ({
value: column, text: i18n.ts._deck._columns[column], value: column, label: i18n.ts._deck._columns[column],
})), })),
}); });
if (canceled || column == null) return; if (canceled || column == null) return;

View File

@ -58,14 +58,15 @@ watch(soundSetting, v => {
async function setChannel() { async function setChannel() {
const channels = await favoritedChannelsCache.fetch(); const channels = await favoritedChannelsCache.fetch();
const { canceled, result: chosenChannel } = await os.select({ const { canceled, result: chosenChannelId } = await os.select({
title: i18n.ts.selectChannel, title: i18n.ts.selectChannel,
items: channels.map(x => ({ items: channels.map(x => ({
value: x, text: x.name, value: x.id, label: x.name,
})), })),
default: props.column.channelId, default: props.column.channelId,
}); });
if (canceled || chosenChannel == null) return; if (canceled || chosenChannelId == null) return;
const chosenChannel = channels.find(x => x.id === chosenChannelId)!;
updateColumn(props.column.id, { updateColumn(props.column.id, {
channelId: chosenChannel.id, channelId: chosenChannel.id,
timelineNameCache: chosenChannel.name, timelineNameCache: chosenChannel.name,

View File

@ -49,14 +49,15 @@ watch(soundSetting, v => {
async function setRole() { async function setRole() {
const roles = (await misskeyApi('roles/list')).filter(x => x.isExplorable); const roles = (await misskeyApi('roles/list')).filter(x => x.isExplorable);
const { canceled, result: role } = await os.select({ const { canceled, result: roleId } = await os.select({
title: i18n.ts.role, title: i18n.ts.role,
items: roles.map(x => ({ items: roles.map(x => ({
value: x, text: x.name, value: x.id, label: x.name,
})), })),
default: props.column.roleId, default: props.column.roleId,
}); });
if (canceled || role == null) return; if (canceled || roleId == null) return;
const role = roles.find(x => x.id === roleId)!;
updateColumn(props.column.id, { updateColumn(props.column.id, {
roleId: role.id, roleId: role.id,
timelineNameCache: role.name, timelineNameCache: role.name,

View File

@ -96,13 +96,13 @@ async function setType() {
const { canceled, result: src } = await os.select({ const { canceled, result: src } = await os.select({
title: i18n.ts.timeline, title: i18n.ts.timeline,
items: [{ items: [{
value: 'home' as const, text: i18n.ts._timelines.home, value: 'home', label: i18n.ts._timelines.home,
}, { }, {
value: 'local' as const, text: i18n.ts._timelines.local, value: 'local', label: i18n.ts._timelines.local,
}, { }, {
value: 'social' as const, text: i18n.ts._timelines.social, value: 'social', label: i18n.ts._timelines.social,
}, { }, {
value: 'global' as const, text: i18n.ts._timelines.global, value: 'global', label: i18n.ts._timelines.global,
}], }],
}); });
if (canceled) { if (canceled) {

View File

@ -37,15 +37,15 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
const { canceled, result: period } = await os.select({ const { canceled, result: period } = await os.select({
title: i18n.ts.mutePeriod, title: i18n.ts.mutePeriod,
items: [{ items: [{
value: 'indefinitely', text: i18n.ts.indefinitely, value: 'indefinitely', label: i18n.ts.indefinitely,
}, { }, {
value: 'tenMinutes', text: i18n.ts.tenMinutes, value: 'tenMinutes', label: i18n.ts.tenMinutes,
}, { }, {
value: 'oneHour', text: i18n.ts.oneHour, value: 'oneHour', label: i18n.ts.oneHour,
}, { }, {
value: 'oneDay', text: i18n.ts.oneDay, value: 'oneDay', label: i18n.ts.oneDay,
}, { }, {
value: 'oneWeek', text: i18n.ts.oneWeek, value: 'oneWeek', label: i18n.ts.oneWeek,
}], }],
default: 'indefinitely', default: 'indefinitely',
}); });
@ -313,15 +313,15 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
const { canceled, result: period } = await os.select({ const { canceled, result: period } = await os.select({
title: i18n.ts.period + ': ' + r.name, title: i18n.ts.period + ': ' + r.name,
items: [{ items: [{
value: 'indefinitely', text: i18n.ts.indefinitely, value: 'indefinitely', label: i18n.ts.indefinitely,
}, { }, {
value: 'oneHour', text: i18n.ts.oneHour, value: 'oneHour', label: i18n.ts.oneHour,
}, { }, {
value: 'oneDay', text: i18n.ts.oneDay, value: 'oneDay', label: i18n.ts.oneDay,
}, { }, {
value: 'oneWeek', text: i18n.ts.oneWeek, value: 'oneWeek', label: i18n.ts.oneWeek,
}, { }, {
value: 'oneMonth', text: i18n.ts.oneMonth, value: 'oneMonth', label: i18n.ts.oneMonth,
}], }],
default: 'indefinitely', default: 'indefinitely',
}); });

View File

@ -67,15 +67,15 @@ const fetching = ref(true);
async function chooseList() { async function chooseList() {
const lists = await misskeyApi('users/lists/list'); const lists = await misskeyApi('users/lists/list');
const { canceled, result: list } = await os.select({ const { canceled, result: listId } = await os.select({
title: i18n.ts.selectList, title: i18n.ts.selectList,
items: lists.map(x => ({ items: lists.map(x => ({
value: x, text: x.name, value: x.id, label: x.name,
})), })),
default: widgetProps.listId, default: widgetProps.listId,
}); });
if (canceled || list == null) return; if (canceled || listId == null) return;
const list = lists.find(x => x.id === listId)!;
widgetProps.listId = list.id; widgetProps.listId = list.id;
save(); save();
fetch(); fetch();