import { useCallback, useEffect, useMemo, useState } from 'react' import FieldError from '@/components/common/FieldError' import FieldWarning from '@/components/common/FieldWarning' import PostCreationDataFields from '@/components/posts/PostCreationDataFields' import PostTextField from '@/components/posts/PostTextField' import PostImportThumbnailPreview from '@/components/posts/import/PostImportThumbnailPreview' import type { FC } from 'react' import type { PostImportEditableDraft, PostImportRow } from '@/lib/postImportSession' import type { DialogueFormControls } from '@/lib/dialogues/useDialogue' type Draft = PostImportEditableDraft 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 ?? ''), 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 ?? ''), tags: String (row.resetSnapshot.attributes.tags ?? ''), parentPostIds: String (row.resetSnapshot.attributes.parentPostIds ?? '') }) const groupedMessages = (...values: (string[] | undefined)[]): string[] => [...new Set (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.tags === right.tags && left.parentPostIds === right.parentPostIds const sameProvenance = ( current: PostImportRow['provenance'], reset: PostImportRow['resetSnapshot']['provenance'], ): boolean => Object.keys (reset).every (field => current[field] === reset[field]) const sameTagSources = ( current: PostImportRow['tagSources'], reset: PostImportRow['resetSnapshot']['tagSources'], ): boolean => (current?.automatic ?? '') === reset.automatic && (current?.manual ?? '') === reset.manual 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) const [committedThumbnailBase, setCommittedThumbnailBase] = useState ( () => String (row.attributes.thumbnailBase ?? '')) useEffect (() => { const nextDraft = buildDraft (row) setDraft (nextDraft) setMessageRow (null) setResetRequested (false) setCommittedThumbnailBase (String (row.attributes.thumbnailBase ?? '')) }, [row]) const displayRow = messageRow ?? row const resetDraft = useMemo ( () => buildResetDraft (row), [row]) const resetDisabled = saving || (sameDraft (draft, resetDraft) && sameProvenance (row.provenance, row.resetSnapshot.provenance) && sameTagSources (row.tagSources, row.resetSnapshot.tagSources) && row.metadataUrl === row.resetSnapshot.metadataUrl) 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) setCommittedThumbnailBase (resetDraft.thumbnailBase) 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), disabled: saving, warnings: displayRow.fieldWarnings.url, errors: groupedMessages ( displayRow.validationErrors.url, displayRow.importErrors?.url) }} thumbnailField={ <> { if (draft.thumbnailBase !== committedThumbnailBase) setCommittedThumbnailBase (draft.thumbnailBase) }} onChange={value => update ('thumbnailBase', value)}/> } core={{ title: { value: draft.title, onChange: value => update ('title', value), disabled: saving, warnings: displayRow.fieldWarnings.title, errors: groupedMessages ( displayRow.validationErrors.title, displayRow.importErrors?.title) }, originalCreated: { disabled: saving, originalCreatedFrom: draft.originalCreatedFrom || null, setOriginalCreatedFrom: value => update ('originalCreatedFrom', value ?? ''), originalCreatedBefore: draft.originalCreatedBefore || null, setOriginalCreatedBefore: value => update ('originalCreatedBefore', value ?? ''), errors: { originalCreatedAt: groupedMessages ( displayRow.validationErrors.originalCreatedAt, displayRow.importErrors?.originalCreatedAt), originalCreatedFrom: groupedMessages ( displayRow.validationErrors.originalCreatedFrom, displayRow.importErrors?.originalCreatedFrom), originalCreatedBefore: groupedMessages ( displayRow.validationErrors.originalCreatedBefore, displayRow.importErrors?.originalCreatedBefore) } }, tags: { value: draft.tags, onChange: value => update ('tags', value), disabled: saving, warnings: displayRow.fieldWarnings.tags, errors: groupedMessages ( displayRow.validationErrors.tags, displayRow.importErrors?.tags), rows: 4 }, parentPostIds: { value: draft.parentPostIds, onChange: value => update ('parentPostIds', value), disabled: saving, errors: groupedMessages ( displayRow.validationErrors.parentPostIds, displayRow.importErrors?.parentPostIds) } }}/>
) } export default PostImportRowForm export { buildDraft } export type { Draft as PostImportRowDraft }