refactor(frontend): os.ts周りのリファクタリング (#13186)
				
					
				
			* refactor(frontend): `os.ts`周りのリファクタリング * refactor: apiWithDialogのdataの型付け * refactor: 不要なas anyを除去 * refactor: 返り値の型を明記、`selectDriveFolder`は`File`のほうに合わせるよう返り値を変更 * refactor: 返り値の型を改善 * refactor: フォームの型を改善 * refactor: 良い感じのimportに修正 * refactor: フォームの返り値の型を改善 * refactor: `popup()`の`props`に`ref`な値を入れるのを許可するように * fix: `os.input`系と`os.select`の返り値の型がおかしい問題とそれによるバグを修正 * Update CHANGELOG.md * Update CHANGELOG.md --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
		| @@ -38,11 +38,6 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 			<template v-if="select.items"> | ||||
| 				<option v-for="item in select.items" :value="item.value">{{ item.text }}</option> | ||||
| 			</template> | ||||
| 			<template v-else> | ||||
| 				<optgroup v-for="groupedItem in select.groupedItems" :label="groupedItem.label"> | ||||
| 					<option v-for="item in groupedItem.items" :value="item.value">{{ item.text }}</option> | ||||
| 				</optgroup> | ||||
| 			</template> | ||||
| 		</MkSelect> | ||||
| 		<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons"> | ||||
| 			<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton> | ||||
| @@ -64,7 +59,7 @@ import MkSelect from '@/components/MkSelect.vue'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
|  | ||||
| type Input = { | ||||
| 	type: 'text' | 'number' | 'password' | 'email' | 'url' | 'date' | 'time' | 'search' | 'datetime-local'; | ||||
| 	type?: 'text' | 'number' | 'password' | 'email' | 'url' | 'date' | 'time' | 'search' | 'datetime-local'; | ||||
| 	placeholder?: string | null; | ||||
| 	autocomplete?: string; | ||||
| 	default: string | number | null; | ||||
| @@ -74,22 +69,17 @@ type Input = { | ||||
|  | ||||
| type Select = { | ||||
| 	items: { | ||||
| 		value: string; | ||||
| 		value: any; | ||||
| 		text: string; | ||||
| 	}[]; | ||||
| 	groupedItems: { | ||||
| 		label: string; | ||||
| 		items: { | ||||
| 			value: string; | ||||
| 			text: string; | ||||
| 		}[]; | ||||
| 	}[]; | ||||
| 	default: string | null; | ||||
| }; | ||||
|  | ||||
| type Result = string | number | true | null; | ||||
|  | ||||
| const props = withDefaults(defineProps<{ | ||||
| 	type?: 'success' | 'error' | 'warning' | 'info' | 'question' | 'waiting'; | ||||
| 	title: string; | ||||
| 	title?: string; | ||||
| 	text?: string; | ||||
| 	input?: Input; | ||||
| 	select?: Select; | ||||
| @@ -113,7 +103,7 @@ const props = withDefaults(defineProps<{ | ||||
| }); | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
| 	(ev: 'done', v: { canceled: boolean; result: any }): void; | ||||
| 	(ev: 'done', v: { canceled: true } | { canceled: false, result: Result }): void; | ||||
| 	(ev: 'closed'): void; | ||||
| }>(); | ||||
|  | ||||
| @@ -139,8 +129,11 @@ const okButtonDisabledReason = computed<null | 'charactersExceeded' | 'character | ||||
| 	return null; | ||||
| }); | ||||
|  | ||||
| function done(canceled: boolean, result?) { | ||||
| 	emit('done', { canceled, result }); | ||||
| // overload function を使いたいので lint エラーを無視する | ||||
| function done(canceled: true): void; | ||||
| function done(canceled: false, result: Result): void; // eslint-disable-line no-redeclare | ||||
| function done(canceled: boolean, result?: Result): void { // eslint-disable-line no-redeclare | ||||
| 	emit('done', { canceled, result } as { canceled: true } | { canceled: false, result: Result }); | ||||
| 	modal.value?.close(); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -39,13 +39,13 @@ withDefaults(defineProps<{ | ||||
| }); | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
| 	(ev: 'done', r?: Misskey.entities.DriveFile[]): void; | ||||
| 	(ev: 'done', r?: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void; | ||||
| 	(ev: 'closed'): void; | ||||
| }>(); | ||||
|  | ||||
| const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||
|  | ||||
| const selected = ref<Misskey.entities.DriveFile[]>([]); | ||||
| const selected = ref<Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]>([]); | ||||
|  | ||||
| function ok() { | ||||
| 	emit('done', selected.value); | ||||
| @@ -57,7 +57,7 @@ function cancel() { | ||||
| 	dialog.value?.close(); | ||||
| } | ||||
|  | ||||
| function onChangeSelection(files: Misskey.entities.DriveFile[]) { | ||||
| 	selected.value = files; | ||||
| function onChangeSelection(v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]) { | ||||
| 	selected.value = v; | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -56,7 +56,7 @@ const props = withDefaults(defineProps<{ | ||||
| }); | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
| 	(ev: 'done', v: any): void; | ||||
| 	(ev: 'done', v: string): void; | ||||
| 	(ev: 'close'): void; | ||||
| 	(ev: 'closed'): void; | ||||
| }>(); | ||||
| @@ -64,7 +64,7 @@ const emit = defineEmits<{ | ||||
| const modal = shallowRef<InstanceType<typeof MkModal>>(); | ||||
| const picker = shallowRef<InstanceType<typeof MkEmojiPicker>>(); | ||||
|  | ||||
| function chosen(emoji: any) { | ||||
| function chosen(emoji: string) { | ||||
| 	emit('done', emoji); | ||||
| 	if (props.choseAndClose) { | ||||
| 		modal.value?.close(); | ||||
|   | ||||
| @@ -1,49 +0,0 @@ | ||||
| <!-- | ||||
| SPDX-FileCopyrightText: syuilo and misskey-project | ||||
| SPDX-License-Identifier: AGPL-3.0-only | ||||
| --> | ||||
|  | ||||
| <template> | ||||
| <MkWindow | ||||
| 	ref="window" | ||||
| 	:initialWidth="300" | ||||
| 	:initialHeight="290" | ||||
| 	:canResize="true" | ||||
| 	:mini="true" | ||||
| 	:front="true" | ||||
| 	@closed="emit('closed')" | ||||
| > | ||||
| 	<MkEmojiPicker :showPinned="showPinned" :asReactionPicker="asReactionPicker" :targetNote="targetNote" asWindow :class="$style.picker" @chosen="chosen"/> | ||||
| </MkWindow> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import MkWindow from '@/components/MkWindow.vue'; | ||||
| import MkEmojiPicker from '@/components/MkEmojiPicker.vue'; | ||||
|  | ||||
| withDefaults(defineProps<{ | ||||
| 	src?: HTMLElement; | ||||
| 	showPinned?: boolean; | ||||
| 	asReactionPicker?: boolean; | ||||
| 	targetNote?: Misskey.entities.Note | ||||
| }>(), { | ||||
| 	showPinned: true, | ||||
| }); | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
| 	(ev: 'chosen', v: any): void; | ||||
| 	(ev: 'closed'): void; | ||||
| }>(); | ||||
|  | ||||
| function chosen(emoji: any) { | ||||
| 	emit('chosen', emoji); | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" module> | ||||
| .picker { | ||||
| 	height: 100%; | ||||
| } | ||||
| </style> | ||||
| @@ -21,37 +21,37 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
|  | ||||
| 	<MkSpacer :marginMin="20" :marginMax="32"> | ||||
| 		<div v-if="Object.keys(form).filter(item => !form[item].hidden).length > 0" class="_gaps_m"> | ||||
| 			<template v-for="item in Object.keys(form).filter(item => !form[item].hidden)"> | ||||
| 				<MkInput v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1"> | ||||
| 					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template> | ||||
| 					<template v-if="form[item].description" #caption>{{ form[item].description }}</template> | ||||
| 			<template v-for="(v, k) in Object.fromEntries(Object.entries(form).filter(([_, v]) => !('hidden' in v) || 'hidden' in v && !v.hidden))"> | ||||
| 				<MkInput v-if="v.type === 'number'" v-model="values[k]" type="number" :step="v.step || 1"> | ||||
| 					<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template> | ||||
| 					<template v-if="v.description" #caption>{{ v.description }}</template> | ||||
| 				</MkInput> | ||||
| 				<MkInput v-else-if="form[item].type === 'string' && !form[item].multiline" v-model="values[item]" type="text" :mfmAutocomplete="form[item].treatAsMfm"> | ||||
| 					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template> | ||||
| 					<template v-if="form[item].description" #caption>{{ form[item].description }}</template> | ||||
| 				<MkInput v-else-if="v.type === 'string' && !v.multiline" v-model="values[k]" type="text" :mfmAutocomplete="v.treatAsMfm"> | ||||
| 					<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template> | ||||
| 					<template v-if="v.description" #caption>{{ v.description }}</template> | ||||
| 				</MkInput> | ||||
| 				<MkTextarea v-else-if="form[item].type === 'string' && form[item].multiline" v-model="values[item]" :mfmAutocomplete="form[item].treatAsMfm" :mfmPreview="form[item].treatAsMfm"> | ||||
| 					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template> | ||||
| 					<template v-if="form[item].description" #caption>{{ form[item].description }}</template> | ||||
| 				<MkTextarea v-else-if="v.type === 'string' && v.multiline" v-model="values[k]" :mfmAutocomplete="v.treatAsMfm" :mfmPreview="v.treatAsMfm"> | ||||
| 					<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template> | ||||
| 					<template v-if="v.description" #caption>{{ v.description }}</template> | ||||
| 				</MkTextarea> | ||||
| 				<MkSwitch v-else-if="form[item].type === 'boolean'" v-model="values[item]"> | ||||
| 					<span v-text="form[item].label || item"></span> | ||||
| 					<template v-if="form[item].description" #caption>{{ form[item].description }}</template> | ||||
| 				<MkSwitch v-else-if="v.type === 'boolean'" v-model="values[k]"> | ||||
| 					<span v-text="v.label || k"></span> | ||||
| 					<template v-if="v.description" #caption>{{ v.description }}</template> | ||||
| 				</MkSwitch> | ||||
| 				<MkSelect v-else-if="form[item].type === 'enum'" v-model="values[item]"> | ||||
| 					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template> | ||||
| 					<option v-for="option in form[item].enum" :key="option.value" :value="option.value">{{ option.label }}</option> | ||||
| 				<MkSelect v-else-if="v.type === 'enum'" v-model="values[k]"> | ||||
| 					<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template> | ||||
| 					<option v-for="option in v.enum" :key="option.value" :value="option.value">{{ option.label }}</option> | ||||
| 				</MkSelect> | ||||
| 				<MkRadios v-else-if="form[item].type === 'radio'" v-model="values[item]"> | ||||
| 					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template> | ||||
| 					<option v-for="option in form[item].options" :key="option.value" :value="option.value">{{ option.label }}</option> | ||||
| 				<MkRadios v-else-if="v.type === 'radio'" v-model="values[k]"> | ||||
| 					<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template> | ||||
| 					<option v-for="option in v.options" :key="option.value" :value="option.value">{{ option.label }}</option> | ||||
| 				</MkRadios> | ||||
| 				<MkRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].min" :max="form[item].max" :step="form[item].step" :textConverter="form[item].textConverter"> | ||||
| 					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template> | ||||
| 					<template v-if="form[item].description" #caption>{{ form[item].description }}</template> | ||||
| 				<MkRange v-else-if="v.type === 'range'" v-model="values[k]" :min="v.min" :max="v.max" :step="v.step" :textConverter="v.textConverter"> | ||||
| 					<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template> | ||||
| 					<template v-if="v.description" #caption>{{ v.description }}</template> | ||||
| 				</MkRange> | ||||
| 				<MkButton v-else-if="form[item].type === 'button'" @click="form[item].action($event, values)"> | ||||
| 					<span v-text="form[item].content || item"></span> | ||||
| 				<MkButton v-else-if="v.type === 'button'" @click="v.action($event, values)"> | ||||
| 					<span v-text="v.content || k"></span> | ||||
| 				</MkButton> | ||||
| 			</template> | ||||
| 		</div> | ||||
| @@ -72,19 +72,21 @@ import MkSelect from './MkSelect.vue'; | ||||
| import MkRange from './MkRange.vue'; | ||||
| import MkButton from './MkButton.vue'; | ||||
| import MkRadios from './MkRadios.vue'; | ||||
| import type { Form } from '@/scripts/form.js'; | ||||
| import MkModalWindow from '@/components/MkModalWindow.vue'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
| import { infoImageUrl } from '@/instance.js'; | ||||
|  | ||||
| const props = defineProps<{ | ||||
| 	title: string; | ||||
| 	form: any; | ||||
| 	form: Form; | ||||
| }>(); | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
| 	(ev: 'done', v: { | ||||
| 		canceled?: boolean; | ||||
| 		result?: any; | ||||
| 		canceled: true; | ||||
| 	} | { | ||||
| 		result: Record<string, any>; | ||||
| 	}): void; | ||||
| 	(ev: 'closed'): void; | ||||
| }>(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 zyoshoka
					zyoshoka