import { useCallback, useEffect, useMemo, useState } from 'react' import PostOriginalCreatedTimeField from '@/components/PostOriginalCreatedTimeField' import FieldError from '@/components/common/FieldError' import FieldWarning from '@/components/common/FieldWarning' import FormField from '@/components/common/FormField' import TextArea from '@/components/common/TextArea' import ThumbnailPreview from '@/components/posts/import/ThumbnailPreview' import { inputClass } from '@/lib/utils' import type { FC } from 'react' import type { PostImportRow } from '@/lib/postImportSession' import type { DialogueFormControls } from '@/lib/dialogues/useDialogue' type Draft = { url: string title: string thumbnailBase: string originalCreatedFrom: string originalCreatedBefore: string duration: string tags: string parentPostIds: string } type Props = { row: PostImportRow controls: DialogueFormControls onSave: (args: { draft: Draft resetRequested: boolean }) => Promise<{ saved: boolean row: PostImportRow | null }> } const buildDraft = (row: PostImportRow): Draft => ({ url: row.url, title: String (row.attributes.title ?? ''), thumbnailBase: String (row.attributes.thumbnailBase ?? ''), originalCreatedFrom: String (row.attributes.originalCreatedFrom ?? ''), originalCreatedBefore: String (row.attributes.originalCreatedBefore ?? ''), duration: String (row.attributes.duration ?? ''), tags: String (row.attributes.tags ?? ''), parentPostIds: String (row.attributes.parentPostIds ?? '') }) const buildResetDraft = (row: PostImportRow): Draft => ({ url: row.resetSnapshot.url, title: String (row.resetSnapshot.attributes.title ?? ''), thumbnailBase: String (row.resetSnapshot.attributes.thumbnailBase ?? ''), originalCreatedFrom: String (row.resetSnapshot.attributes.originalCreatedFrom ?? ''), originalCreatedBefore: String (row.resetSnapshot.attributes.originalCreatedBefore ?? ''), duration: String (row.resetSnapshot.attributes.duration ?? ''), tags: String (row.resetSnapshot.attributes.tags ?? ''), parentPostIds: String (row.resetSnapshot.attributes.parentPostIds ?? '') }) const groupedMessages = (...values: (string[] | undefined)[]): string[] => values.flatMap (value => value ?? []) const sameDraft = (left: Draft, right: Draft): boolean => left.url === right.url && left.title === right.title && left.thumbnailBase === right.thumbnailBase && left.originalCreatedFrom === right.originalCreatedFrom && left.originalCreatedBefore === right.originalCreatedBefore && left.duration === right.duration && left.tags === right.tags && left.parentPostIds === right.parentPostIds const PostImportRowForm: FC = ( { row, controls, onSave }, ) => { const [draft, setDraft] = useState (() => buildDraft (row)) const [messageRow, setMessageRow] = useState (null) const [saving, setSaving] = useState (false) const [resetRequested, setResetRequested] = useState (false) useEffect (() => { const nextDraft = buildDraft (row) setDraft (nextDraft) setMessageRow (null) setResetRequested (false) }, [row]) const displayRow = messageRow ?? row const resetDraft = useMemo ( () => buildResetDraft (row), [row]) const resetDisabled = saving || sameDraft (draft, resetDraft) const update = ( key: Key, value: Draft[Key], ) => { if (messageRow != null) setMessageRow (null) setDraft (current => ({ ...current, [key]: value })) } const reset = useCallback (async (): Promise => { if (resetDisabled) return false const confirmed = await controls.confirm ({ title: '変更をリセットしますか?', description: '現在の URL に対する自動取得直後の内容へ戻します.', confirmText: 'リセット', cancelText: '取消', variant: 'danger' }) if (!(confirmed)) return false setDraft (resetDraft) setResetRequested (true) setMessageRow (null) return false }, [controls, resetDisabled, resetDraft]) const save = useCallback (async (): Promise => { setSaving (true) try { const result = await onSave ({ draft, resetRequested }) if (result.saved) return true if (result.row != null) setMessageRow (result.row) return false } finally { setSaving (false) } }, [draft, onSave, resetRequested]) useEffect (() => { controls.setActions ([{ label: '変更をリセット', placement: 'start', variant: 'danger', disabled: resetDisabled, onSelect: reset }, { label: '編輯内容を保存', disabled: saving, onSelect: save }]) }, [controls, resetDisabled, reset, save, saving]) return ( <>
update ('url', value)}/> update ('title', value)}/> update ('thumbnailBase', value)}/> update ('originalCreatedFrom', value ?? '')} originalCreatedBefore={draft.originalCreatedBefore || null} setOriginalCreatedBefore={value => update ('originalCreatedBefore', value ?? '')} errors={groupedMessages ( displayRow.validationErrors.originalCreatedAt, displayRow.validationErrors.originalCreatedFrom, displayRow.validationErrors.originalCreatedBefore, displayRow.importErrors?.originalCreatedAt, displayRow.importErrors?.originalCreatedFrom, displayRow.importErrors?.originalCreatedBefore)}/> update ('duration', value)}/> update ('tags', value)}/> update ('parentPostIds', value)}/>
) } const PostImportTextField = ( { label, value, warnings, errors, onChange }: { label: string value: string warnings?: string[] errors?: string[] onChange: (value: string) => void }, ) => ( {({ describedBy, invalid }) => ( <> onChange (ev.target.value)} aria-describedby={describedBy} aria-invalid={invalid} className={inputClass (invalid)}/> )} ) const PostImportAreaField = ( { label, value, warnings, errors, onChange }: { label: string value: string warnings?: string[] errors?: string[] onChange: (value: string) => void }, ) => ( {({ describedBy, invalid }) => ( <>