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 MkInput from '@/components/MkInput.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 { i18n } from '@/i18n.js';
@ -62,7 +62,7 @@ type Input = {
type Select = {
items: MkSelectItem[];
default: string | null;
default: OptionValue | null;
};
type Result = string | number | true | null;

View File

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

View File

@ -102,12 +102,12 @@ async function addRole() {
const items = roles.value
.filter(r => r.isPublic)
.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 });
if (canceled || role == null) return;
const { canceled, result: roleId } = await os.select({ items });
if (canceled || roleId == null) return;
selectedRoleIds.value.push(role.id);
selectedRoleIds.value.push(roleId);
}
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 { PostFormProps } from '@/types/post-form.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 MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
@ -502,50 +503,15 @@ export function authenticateDialog(): Promise<{
});
}
type SelectItem<C> = {
value: C;
text: string;
};
// default が指定されていたら result は null になり得ないことを保証する overload function
export function select<C = unknown>(props: {
export function select<C extends OptionValue, D extends C | null = null>(props: {
title?: string;
text?: string;
default: string;
items: (SelectItem<C> | {
sectionTitle: string;
items: SelectItem<C>[];
} | undefined)[];
default?: D;
items: (MkSelectItem<C> | undefined)[];
}): Promise<{
canceled: true; result: undefined;
} | {
canceled: false; result: 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;
canceled: false; result: Exclude<D, undefined> extends null ? C | null : C;
}> {
return new Promise(resolve => {
const { dispose } = popup(MkDialog, {

View File

@ -442,22 +442,22 @@ async function assignRole() {
const { canceled, result: roleId } = await os.select({
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({
title: i18n.ts.period + ': ' + roles.find(r => r.id === roleId)!.name,
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',
});

View File

@ -115,15 +115,15 @@ async function assign() {
const { canceled: canceled2, result: period } = await os.select({
title: i18n.ts.period + ': ' + role.name,
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',
});

View File

@ -101,12 +101,12 @@ async function addRole() {
const roles = await misskeyApi('admin/roles/list');
const currentRoleIds = rolesThatCanBeUsedThisDecoration.value.map(x => x.id);
const { canceled, result: role } = await os.select({
items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ text: r.name, value: r })),
const { canceled, result: roleId } = await os.select({
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) {

View File

@ -135,12 +135,12 @@ async function addRole() {
const roles = await misskeyApi('admin/roles/list');
const currentRoleIds = rolesThatCanBeUsedThisEmojiAsReaction.value.map(x => x.id);
const { canceled, result: role } = await os.select({
items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ text: r.name, value: r })),
const { canceled, result: roleId } = await os.select({
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) {

View File

@ -4,12 +4,13 @@
*/
import { i18n } from '@/i18n.js';
import type { MkSelectItem } from '@/components/MkSelect.vue';
export function getPageBlockList() {
return [
{ value: 'section', text: i18n.ts._pages.blocks.section },
{ value: 'text', text: i18n.ts._pages.blocks.text },
{ value: 'image', text: i18n.ts._pages.blocks.image },
{ value: 'note', text: i18n.ts._pages.blocks.note },
];
{ value: 'section', label: i18n.ts._pages.blocks.section },
{ value: 'text', label: i18n.ts._pages.blocks.text },
{ value: 'image', label: i18n.ts._pages.blocks.image },
{ 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,
items: getPageBlockList(),
});
if (canceled) return;
if (canceled || type == null) return;
const id = genId();
children.value.push({ id, type });

View File

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

View File

@ -86,9 +86,9 @@ async function addItem() {
const { canceled, result: item } = await os.select({
title: i18n.ts.addItem,
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;

View File

@ -1010,16 +1010,15 @@ function removeEmojiIndex(lang: string) {
async function setPinnedList() {
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,
items: lists.map(x => ({
value: x, text: x.name,
value: x.id, label: x.name,
})),
});
if (canceled) return;
if (list == null) return;
if (canceled || listId == null) return;
prefer.commit('pinnedUserLists', [list]);
prefer.commit('pinnedUserLists', [lists.find((x) => x.id === listId)!]);
}
function removePinnedList() {

View File

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

View File

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

View File

@ -168,7 +168,7 @@ const addColumn = async (ev) => {
const { canceled, result: column } = await os.select({
title: i18n.ts._deck.addColumn,
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;

View File

@ -58,14 +58,15 @@ watch(soundSetting, v => {
async function setChannel() {
const channels = await favoritedChannelsCache.fetch();
const { canceled, result: chosenChannel } = await os.select({
const { canceled, result: chosenChannelId } = await os.select({
title: i18n.ts.selectChannel,
items: channels.map(x => ({
value: x, text: x.name,
value: x.id, label: x.name,
})),
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, {
channelId: chosenChannel.id,
timelineNameCache: chosenChannel.name,

View File

@ -49,14 +49,15 @@ watch(soundSetting, v => {
async function setRole() {
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,
items: roles.map(x => ({
value: x, text: x.name,
value: x.id, label: x.name,
})),
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, {
roleId: role.id,
timelineNameCache: role.name,

View File

@ -96,13 +96,13 @@ async function setType() {
const { canceled, result: src } = await os.select({
title: i18n.ts.timeline,
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) {

View File

@ -37,15 +37,15 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
const { canceled, result: period } = await os.select({
title: i18n.ts.mutePeriod,
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',
});
@ -313,15 +313,15 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
const { canceled, result: period } = await os.select({
title: i18n.ts.period + ': ' + r.name,
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',
});

View File

@ -67,15 +67,15 @@ const fetching = ref(true);
async function chooseList() {
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,
items: lists.map(x => ({
value: x, text: x.name,
value: x.id, label: x.name,
})),
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;
save();
fetch();