From a8abb03d1785791ab40e57ab49c87640914532c9 Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sun, 6 Jul 2025 19:36:11 +0900 Subject: refactor(frontend): Formまわりの型強化 (#16260) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(frontend): Formまわりの型強化 * fix * avoid non-null assertion and add null check for safety * refactor * avoid non-null assertion and add null check for safety * Update clip.vue --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- packages/frontend/src/utility/form.ts | 122 +++++++++++++++++-------- packages/frontend/src/utility/get-note-menu.ts | 2 +- packages/frontend/src/utility/get-user-menu.ts | 2 + 3 files changed, 85 insertions(+), 41 deletions(-) (limited to 'packages/frontend/src/utility') diff --git a/packages/frontend/src/utility/form.ts b/packages/frontend/src/utility/form.ts index 1032e97ac9..2b765dc714 100644 --- a/packages/frontend/src/utility/form.ts +++ b/packages/frontend/src/utility/form.ts @@ -5,55 +5,59 @@ import * as Misskey from 'misskey-js'; -type EnumItem = string | { +export type EnumItem = string | { label: string; - value: string; + value: unknown; }; type Hidden = boolean | ((v: any) => boolean); -export type FormItem = { +interface FormItemBase { label?: string; + hidden?: Hidden; +} + +export interface StringFormItem extends FormItemBase { type: 'string'; default?: string | null; description?: string; required?: boolean; - hidden?: Hidden; multiline?: boolean; treatAsMfm?: boolean; -} | { - label?: string; +} + +export interface NumberFormItem extends FormItemBase { type: 'number'; default?: number | null; description?: string; required?: boolean; - hidden?: Hidden; step?: number; -} | { - label?: string; +} + +export interface BooleanFormItem extends FormItemBase { type: 'boolean'; default?: boolean | null; description?: string; - hidden?: Hidden; -} | { - label?: string; +} + +export interface EnumFormItem extends FormItemBase { type: 'enum'; default?: string | null; required?: boolean; - hidden?: Hidden; enum: EnumItem[]; -} | { - label?: string; +} + +export interface RadioFormItem extends FormItemBase { type: 'radio'; default?: unknown | null; required?: boolean; - hidden?: Hidden; options: { label: string; value: unknown; }[]; -} | { - label?: string; +} + +export interface RangeFormItem extends FormItemBase { type: 'range'; default?: number | null; description?: string; @@ -62,42 +66,80 @@ export type FormItem = { min: number; max: number; textConverter?: (value: number) => string; - hidden?: Hidden; -} | { - label?: string; +} + +export interface ObjectFormItem extends FormItemBase { type: 'object'; default?: Record | null; - hidden: Hidden; -} | { - label?: string; +} + +export interface ArrayFormItem extends FormItemBase { type: 'array'; default?: unknown[] | null; - hidden: Hidden; -} | { +} + +export interface ButtonFormItem extends FormItemBase { type: 'button'; content?: string; - hidden?: Hidden; action: (ev: MouseEvent, v: any) => void; -} | { +} + +export interface DriveFileFormItem extends FormItemBase { type: 'drive-file'; defaultFileId?: string | null; - hidden?: Hidden; validate?: (v: Misskey.entities.DriveFile) => Promise; -}; +} + +export type FormItem = + StringFormItem | + NumberFormItem | + BooleanFormItem | + EnumFormItem | + RadioFormItem | + RangeFormItem | + ObjectFormItem | + ArrayFormItem | + ButtonFormItem | + DriveFileFormItem; export type Form = Record; +export type FormItemWithDefault = FormItem & { + default: unknown; +}; + +export type FormWithDefault = Record; + +type GetRadioItemType = Item['options'][number]['value']; +type GetEnumItemType = E extends { value: unknown } ? E['value'] : E; + +type InferDefault = T extends { default: infer D } + ? D extends undefined ? Fallback : D + : Fallback; + +type NonNullableIfRequired = + Item extends { required: false } ? T | null | undefined : NonNullable; + type GetItemType = - Item['type'] extends 'string' ? string : - Item['type'] extends 'number' ? number : - Item['type'] extends 'boolean' ? boolean : - Item['type'] extends 'radio' ? unknown : - Item['type'] extends 'range' ? number : - Item['type'] extends 'enum' ? string : - Item['type'] extends 'array' ? unknown[] : - Item['type'] extends 'object' ? Record : - Item['type'] extends 'drive-file' ? Misskey.entities.DriveFile | undefined : - never; + Item extends StringFormItem + ? NonNullableIfRequired, Item> + : Item extends NumberFormItem + ? NonNullableIfRequired, Item> + : Item extends BooleanFormItem + ? boolean + : Item extends RadioFormItem + ? GetRadioItemType + : Item extends RangeFormItem + ? NonNullableIfRequired, Item> + : Item extends EnumFormItem + ? GetEnumItemType + : Item extends ArrayFormItem + ? NonNullableIfRequired, Item> + : Item extends ObjectFormItem + ? NonNullableIfRequired>, Item> + : Item extends DriveFileFormItem + ? Misskey.entities.DriveFile | undefined + : never; export type GetFormResultType = { [P in keyof F]: GetItemType; diff --git a/packages/frontend/src/utility/get-note-menu.ts b/packages/frontend/src/utility/get-note-menu.ts index ea93444f08..5361c1252d 100644 --- a/packages/frontend/src/utility/get-note-menu.ts +++ b/packages/frontend/src/utility/get-note-menu.ts @@ -101,7 +101,7 @@ export async function getNoteClipMenu(props: { const { canceled, result } = await os.form(i18n.ts.createNewClip, { name: { type: 'string', - default: null, + default: null as string | null, label: i18n.ts.name, }, description: { diff --git a/packages/frontend/src/utility/get-user-menu.ts b/packages/frontend/src/utility/get-user-menu.ts index 5c08b8c462..ad0864019b 100644 --- a/packages/frontend/src/utility/get-user-menu.ts +++ b/packages/frontend/src/utility/get-user-menu.ts @@ -132,6 +132,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router const userDetailed = await misskeyApi('users/show', { userId: user.id, }); + const { canceled, result } = await os.form(i18n.ts.editMemo, { memo: { type: 'string', @@ -141,6 +142,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router default: userDetailed.memo, }, }); + if (canceled) return; os.apiWithDialog('users/update-memo', { -- cgit v1.2.3-freya