このコミットが含まれているのは:
2026-07-15 22:26:58 +09:00
コミット 07ce19e32d
8個のファイルの変更784行の追加264行の削除
-54
ファイルの表示
@@ -1,54 +0,0 @@
import { Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle } from '@/components/ui/dialog'
import { cn } from '@/lib/utils'
import type { FC, ReactNode } from 'react'
type Props = {
open: boolean
onOpenChange: (open: boolean) => void
title: ReactNode
description?: ReactNode
className?: string
bodyClassName?: string
footerClassName?: string
footer?: ReactNode
children: ReactNode }
const CommonDialogue: FC<Props> = (
{ open,
onOpenChange,
title,
description,
className,
bodyClassName,
footerClassName,
footer,
children },
) => (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className={cn ('px-6 pb-6 pt-7', className)}>
<DialogHeader className="pl-8">
<DialogTitle>{title}</DialogTitle>
{description != null && (
<DialogDescription asChild>
<div>{description}</div>
</DialogDescription>)}
</DialogHeader>
<div className={bodyClassName}>{children}</div>
{footer != null && (
<DialogFooter className={footerClassName}>
{footer}
</DialogFooter>)}
</DialogContent>
</Dialog>)
export default CommonDialogue
+63 -51
ファイルの表示
@@ -1,7 +1,12 @@
import { createContext, useCallback, useContext, useMemo, useState } from 'react'
import CommonDialogue from '@/components/dialogues/CommonDialogue'
import { Button } from '@/components/ui/button'
import { Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle } from '@/components/ui/dialog'
import type { FC, ReactNode } from 'react'
@@ -106,60 +111,67 @@ const DialogueProvider: FC<Props> = ({ children }) => {
<DialogueContext.Provider value={api}>
{children}
{active && (
<CommonDialogue
open={Boolean (active)}
onOpenChange={open => {
if (!(open))
closeActive (active.kind !== 'confirm' && null)
}}
title={active.options.title}
description={active.options.description}
bodyClassName="hidden"
footer={
<>
{active.kind === 'confirm' && (
<>
<Button
variant="outline"
onClick={() => closeActive (false)}>
{active.options.cancelText ?? '取消'}
</Button>
<Dialog
open={Boolean (active)}
onOpenChange={open => {
if (!(open))
closeActive (active?.kind !== 'confirm' && null)
}}>
{active && (
<DialogContent className="px-6 pb-6 pt-7">
<DialogHeader className="pl-8">
<DialogTitle>{active.options.title}</DialogTitle>
{active.options.description && (
<DialogDescription asChild>
<div>{active.options.description}</div>
</DialogDescription>)}
</DialogHeader>
<DialogFooter>
{active.kind === 'confirm' && (
<>
<Button
variant="outline"
onClick={() => closeActive (false)}>
{active.options.cancelText ?? '取消'}
</Button>
<Button
variant={(active.options.variant === 'danger')
? 'destructive'
: 'default'}
onClick={() => closeActive (true)}>
{active.options.confirmText ?? '確定'}
</Button>
</>)}
{active.kind === 'alert' && (
<Button onClick={() => closeActive ()}>
{active.options.okText ?? '確定'}
</Button>)}
{active.kind === 'choice' && (
<>
<Button
variant="outline"
onClick={() => closeActive (null)}>
{active.options.cancelText ?? '取消'}
</Button>
{active.options.choices.map (choice => (
<Button
variant={(active.options.variant === 'danger')
key={choice.value}
variant={(choice.variant === 'danger')
? 'destructive'
: 'default'}
onClick={() => closeActive (true)}>
{active.options.confirmText ?? '確定'}
</Button>
</>)}
{active.kind === 'alert' && (
<Button onClick={() => closeActive ()}>
{active.options.okText ?? '確定'}
</Button>)}
{active.kind === 'choice' && (
<>
<Button
variant="outline"
onClick={() => closeActive (null)}>
{active.options.cancelText ?? '取消'}
</Button>
{active.options.choices.map (choice => (
<Button
key={choice.value}
variant={(choice.variant === 'danger')
? 'destructive'
: 'default'}
onClick={() => closeActive (choice.value)}>
{choice.label}
</Button>))}
</>)}
</>}/>
)}
onClick={() => closeActive (choice.value)}>
{choice.label}
</Button>))}
</>)}
</DialogFooter>
</DialogContent>)}
</Dialog>
</DialogueContext.Provider>)
}
+116 -117
ファイルの表示
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { useEffect, useState } from 'react'
import PostOriginalCreatedTimeField from '@/components/PostOriginalCreatedTimeField'
import FieldError from '@/components/common/FieldError'
@@ -9,10 +9,9 @@ import { Button } from '@/components/ui/button'
import { useDialogue } from '@/lib/dialogues/useDialogue'
import { inputClass } from '@/lib/utils'
import type { FC } from 'react'
import type { Dispatch, FC, SetStateAction } from 'react'
import type { PostImportResetSnapshot,
PostImportRow } from '@/lib/postImportSession'
import type { PostImportRow } from '@/lib/postImportSession'
type Draft = {
url: string
@@ -25,15 +24,11 @@ type Draft = {
parentPostIds: string }
type Props = {
row: PostImportRow
messageRow?: PostImportRow | null
saving: boolean
onCancel: () => void
onSave: (
payload: { draft: Draft
resetRequested: boolean
resetSnapshot: PostImportResetSnapshot },
) => Promise<boolean> }
row: PostImportRow
messageRow?: PostImportRow | null
saving: boolean
onDraftChange: Dispatch<SetStateAction<Draft | null>>
onResetRequestedChange: Dispatch<SetStateAction<boolean>> }
const buildDraft = (row: PostImportRow): Draft => ({
url: row.url,
@@ -70,13 +65,24 @@ const sameDraft = (left: Draft, right: Draft): boolean =>
const PostImportRowForm: FC<Props> = (
{ row, messageRow, saving, onCancel, onSave },
{ row,
messageRow,
saving,
onDraftChange,
onResetRequestedChange },
) => {
const dialogue = useDialogue ()
const [draft, setDraft] = useState<Draft> (() => buildDraft (row))
const [resetRequested, setResetRequested] = useState (false)
useEffect (() => {
const nextDraft = buildDraft (row)
setDraft (nextDraft)
setResetRequested (false)
onDraftChange (nextDraft)
onResetRequestedChange (false)
}, [onDraftChange, onResetRequestedChange, row])
const displayRow = messageRow ?? row
const resetDraft = buildResetDraft (row)
const resetDisabled = saving || sameDraft (draft, resetDraft)
@@ -85,7 +91,11 @@ const PostImportRowForm: FC<Props> = (
key: Key,
value: Draft[Key],
) => {
setDraft (current => ({ ...current, [key]: value }))
setDraft (current => {
const next = { ...current, [key]: value }
onDraftChange (next)
return next
})
}
const reset = async () => {
@@ -103,116 +113,103 @@ const PostImportRowForm: FC<Props> = (
setDraft (resetDraft)
setResetRequested (true)
}
const save = async () => {
if (await onSave ({
draft,
resetRequested,
resetSnapshot: row.resetSnapshot }))
onCancel ()
onDraftChange (resetDraft)
onResetRequestedChange (true)
}
return (
<>
<div className="min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 pb-6">
<div className="grid gap-6 md:grid-cols-[7rem_minmax(0,1fr)]">
<div className="space-y-3">
<ThumbnailPreview
url={draft.thumbnailBase}
className="h-28 w-28"/>
</div>
<div className="space-y-4">
<p className="text-sm text-muted-foreground">
稿 {row.sourceRow}
</p>
<div className="space-y-4">
<PostImportTextField
label="URL"
value={draft.url}
warnings={displayRow.fieldWarnings.url}
errors={groupedMessages (
displayRow.validationErrors.url,
displayRow.importErrors?.url)}
onChange={value => update ('url', value)}/>
<PostImportTextField
label="タイトル"
value={draft.title}
warnings={displayRow.fieldWarnings.title}
errors={groupedMessages (
displayRow.validationErrors.title,
displayRow.importErrors?.title)}
onChange={value => update ('title', value)}/>
<PostImportTextField
label="サムネール基底 URL"
value={draft.thumbnailBase}
warnings={displayRow.fieldWarnings.thumbnailBase}
errors={groupedMessages (
displayRow.validationErrors.thumbnailBase,
displayRow.importErrors?.thumbnailBase)}
onChange={value => update ('thumbnailBase', value)}/>
<PostOriginalCreatedTimeField
originalCreatedFrom={draft.originalCreatedFrom || null}
setOriginalCreatedFrom={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)}/>
<PostImportTextField
label="動画時間"
value={draft.duration}
errors={groupedMessages (
displayRow.validationErrors.duration,
displayRow.validationErrors.videoMs,
displayRow.importErrors?.duration,
displayRow.importErrors?.videoMs)}
onChange={value => update ('duration', value)}/>
<PostImportAreaField
label="タグ"
value={draft.tags}
warnings={displayRow.fieldWarnings.tags}
errors={groupedMessages (
displayRow.validationErrors.tags,
displayRow.importErrors?.tags)}
onChange={value => update ('tags', value)}/>
<PostImportTextField
label="親投稿"
value={draft.parentPostIds}
errors={groupedMessages (
displayRow.validationErrors.parentPostIds,
displayRow.importErrors?.parentPostIds)}
onChange={value => update ('parentPostIds', value)}/>
<FieldWarning messages={displayRow.baseWarnings}/>
<FieldError messages={displayRow.validationErrors.base}/>
<FieldError messages={displayRow.importErrors?.base}/>
<div className="grid gap-6 md:grid-cols-[7rem_minmax(0,1fr)]">
<div className="space-y-3">
<ThumbnailPreview
url={draft.thumbnailBase}
className="h-28 w-28"/>
</div>
<div className="space-y-4">
<PostImportTextField
label="URL"
value={draft.url}
warnings={displayRow.fieldWarnings.url}
errors={groupedMessages (
displayRow.validationErrors.url,
displayRow.importErrors?.url)}
onChange={value => update ('url', value)}/>
<PostImportTextField
label="タイトル"
value={draft.title}
warnings={displayRow.fieldWarnings.title}
errors={groupedMessages (
displayRow.validationErrors.title,
displayRow.importErrors?.title)}
onChange={value => update ('title', value)}/>
<PostImportTextField
label="サムネール基底 URL"
value={draft.thumbnailBase}
warnings={displayRow.fieldWarnings.thumbnailBase}
errors={groupedMessages (
displayRow.validationErrors.thumbnailBase,
displayRow.importErrors?.thumbnailBase)}
onChange={value => update ('thumbnailBase', value)}/>
<PostOriginalCreatedTimeField
originalCreatedFrom={draft.originalCreatedFrom || null}
setOriginalCreatedFrom={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)}/>
<PostImportTextField
label="動画時間"
value={draft.duration}
errors={groupedMessages (
displayRow.validationErrors.duration,
displayRow.validationErrors.videoMs,
displayRow.importErrors?.duration,
displayRow.importErrors?.videoMs)}
onChange={value => update ('duration', value)}/>
<PostImportAreaField
label="タグ"
value={draft.tags}
warnings={displayRow.fieldWarnings.tags}
errors={groupedMessages (
displayRow.validationErrors.tags,
displayRow.importErrors?.tags)}
onChange={value => update ('tags', value)}/>
<PostImportTextField
label="親投稿"
value={draft.parentPostIds}
errors={groupedMessages (
displayRow.validationErrors.parentPostIds,
displayRow.importErrors?.parentPostIds)}
onChange={value => update ('parentPostIds', value)}/>
<FieldWarning messages={displayRow.baseWarnings}/>
<FieldError messages={displayRow.validationErrors.base}/>
<FieldError messages={displayRow.importErrors?.base}/>
<div className="pt-2">
<Button
type="button"
variant="destructive"
onClick={() => reset ()}
disabled={resetDisabled}>
</Button>
</div>
</div>
</div>
</div>
</div>
<div className="flex shrink-0 flex-col-reverse gap-2 px-6 pb-6 pt-4 sm:flex-row sm:justify-end">
<Button
type="button"
variant="destructive"
onClick={() => reset ()}
disabled={resetDisabled}>
</Button>
<Button
type="button"
variant="outline"
onClick={onCancel}
disabled={saving}>
</Button>
<Button
type="button"
onClick={() => save ()}
disabled={saving}>
</Button>
</div>
</>)
}
@@ -260,4 +257,6 @@ const PostImportAreaField = (
</FormField>)
export default PostImportRowForm
export { buildDraft }
export type { Draft as PostImportRowDraft }
export type { Draft as PostImportRowDraft }