このコミットが含まれているのは:
@@ -30,8 +30,10 @@ import {
|
||||
clearPostImportSourceDraft,
|
||||
generatePostImportSessionId,
|
||||
hasThumbnailBaseValue,
|
||||
hasErrorMessages,
|
||||
isCompletedReviewRow,
|
||||
isExistingSkipRow,
|
||||
isManualSkipRow,
|
||||
isNonRecoverableFailedRow,
|
||||
mergeImportResults,
|
||||
processableImportRows,
|
||||
@@ -41,7 +43,7 @@ import {
|
||||
reviewSummaryCounts,
|
||||
retryImportRow,
|
||||
loadPostImportSession,
|
||||
serialisedPostImportRowsEqual,
|
||||
compactMessageRecord,
|
||||
savePostImportSession,
|
||||
} from '@/lib/postImportSession'
|
||||
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
|
||||
@@ -102,15 +104,48 @@ const isRepairRow = (row: PostImportRow): boolean =>
|
||||
row.recoverable === true
|
||||
&& (row.importStatus === 'failed'
|
||||
|| (row.importStatus === 'pending'
|
||||
&& Object.keys (row.validationErrors).length > 0))
|
||||
&& hasErrorMessages (row.validationErrors)))
|
||||
|
||||
const DUPLICATE_URL_MESSAGE = 'URL が重複しています.'
|
||||
|
||||
const rowMetadataPending = (row: PostImportRow): boolean =>
|
||||
row.status === 'pending'
|
||||
|
||||
const rowOperationBusy = (
|
||||
row: PostImportRow,
|
||||
submitting: boolean,
|
||||
loadingRow: number | null,
|
||||
): boolean =>
|
||||
submitting
|
||||
|| loadingRow === row.sourceRow
|
||||
|| rowMetadataPending (row)
|
||||
|
||||
|
||||
const rowSkipBusy = (
|
||||
row: PostImportRow,
|
||||
submitting: boolean,
|
||||
loadingRow: number | null,
|
||||
): boolean =>
|
||||
submitting
|
||||
|| loadingRow === row.sourceRow
|
||||
|| (rowMetadataPending (row) && !(isManualSkipRow (row)))
|
||||
|
||||
const shouldFetchMetadata = (row: PostImportRow): boolean =>
|
||||
row.status === 'pending'
|
||||
&& row.skipReason !== 'manual'
|
||||
&& row.importStatus !== 'created'
|
||||
&& row.importStatus !== 'skipped'
|
||||
&& !(isNonRecoverableFailedRow (row))
|
||||
&& row.metadataUrl == null
|
||||
|
||||
const applyDuplicateUrlErrors = (
|
||||
rows: PostImportRow[],
|
||||
): PostImportRow[] => {
|
||||
const counts = rows.reduce<Record<string, number>> ((result, row) => {
|
||||
if (row.url !== '')
|
||||
if (row.url !== ''
|
||||
&& !(isManualSkipRow (row))
|
||||
&& row.importStatus !== 'created'
|
||||
&& row.importStatus !== 'skipped')
|
||||
result[row.url] = (result[row.url] ?? 0) + 1
|
||||
return result
|
||||
}, { })
|
||||
@@ -120,27 +155,30 @@ const applyDuplicateUrlErrors = (
|
||||
message => message !== DUPLICATE_URL_MESSAGE)
|
||||
if ((counts[row.url] ?? 0) < 2)
|
||||
{
|
||||
const nextValidationErrors = {
|
||||
const nextValidationErrors = compactMessageRecord ({
|
||||
...row.validationErrors,
|
||||
url: urlErrors }
|
||||
const hasErrors = Object.values (nextValidationErrors).some (
|
||||
messages => messages.length > 0)
|
||||
url: urlErrors })
|
||||
const hasErrors = hasErrorMessages (nextValidationErrors)
|
||||
return urlErrors.length === (row.validationErrors.url ?? []).length
|
||||
? row
|
||||
: {
|
||||
...row,
|
||||
validationErrors: nextValidationErrors,
|
||||
skipReason:
|
||||
row.skipReason === 'manual'
|
||||
? 'manual'
|
||||
: row.existingPostId != null
|
||||
? 'existing'
|
||||
: undefined,
|
||||
status: hasErrors ? 'error' : row.status }
|
||||
}
|
||||
|
||||
return {
|
||||
...row,
|
||||
skipReason: row.skipReason === 'manual' ? 'manual' : undefined,
|
||||
existingPostId: undefined,
|
||||
existingPost: undefined,
|
||||
validationErrors: {
|
||||
validationErrors: compactMessageRecord ({
|
||||
...row.validationErrors,
|
||||
url: [...new Set ([...urlErrors, DUPLICATE_URL_MESSAGE])] },
|
||||
url: [...new Set ([...urlErrors, DUPLICATE_URL_MESSAGE])] }),
|
||||
status: 'error' }
|
||||
})
|
||||
}
|
||||
@@ -179,8 +217,9 @@ const mergeDryRunRow = (
|
||||
currentRow: PostImportRow,
|
||||
result: PostMetadataResponse,
|
||||
): PostImportRow => {
|
||||
const fieldWarnings = compactMessageRecord (result.fieldWarnings ?? { })
|
||||
const hasWarnings =
|
||||
Object.values (result.fieldWarnings ?? { }).some (messages => messages.length > 0)
|
||||
Object.values (fieldWarnings).some (messages => messages.length > 0)
|
||||
|| (result.baseWarnings?.length ?? 0) > 0
|
||||
return applyThumbnailWarning ({
|
||||
...currentRow,
|
||||
@@ -195,7 +234,7 @@ const mergeDryRunRow = (
|
||||
videoMs: result.videoMs ?? '',
|
||||
tags: result.tags ?? '',
|
||||
parentPostIds: String (result.parentPostIds ?? currentRow.attributes.parentPostIds ?? '') },
|
||||
fieldWarnings: result.fieldWarnings ?? { },
|
||||
fieldWarnings,
|
||||
baseWarnings: result.baseWarnings ?? [],
|
||||
validationErrors: { },
|
||||
importErrors: undefined,
|
||||
@@ -291,9 +330,9 @@ const mergePreviewRow = (
|
||||
currentRow: PostImportRow,
|
||||
preview: PostMetadataResponse,
|
||||
): PostImportRow => {
|
||||
const fieldWarnings = preview.fieldWarnings ?? { }
|
||||
const fieldWarnings = compactMessageRecord (preview.fieldWarnings ?? { })
|
||||
const baseWarnings = preview.baseWarnings ?? []
|
||||
const validationErrors = preview.validationErrors ?? { }
|
||||
const validationErrors = compactMessageRecord (preview.validationErrors ?? { })
|
||||
const metadataChanged = currentRow.metadataUrl !== preview.url
|
||||
const hasWarnings =
|
||||
Object.values (fieldWarnings).some (messages => messages.length > 0)
|
||||
@@ -438,7 +477,20 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
const persistSequenceRef = useRef (0)
|
||||
const previewSequenceRef = useRef (0)
|
||||
|
||||
const persistSession = async (
|
||||
const commitSessionState = (
|
||||
nextSession: PostImportSession,
|
||||
): PostImportSession => {
|
||||
const sessionId =
|
||||
sessionIdRef.current ?? generatePostImportSessionId ()
|
||||
sessionIdRef.current = sessionId
|
||||
const persistedSession = buildSession (nextSession.rows)
|
||||
savePostImportSession (sessionId, persistedSession)
|
||||
sessionRef.current = persistedSession
|
||||
setSession (persistedSession)
|
||||
return persistedSession
|
||||
}
|
||||
|
||||
const syncSessionUrl = async (
|
||||
nextSession: PostImportSession,
|
||||
): Promise<PostImportSession | null> => {
|
||||
const sessionId =
|
||||
@@ -451,26 +503,21 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
if (persistSequenceRef.current !== sequence)
|
||||
return null
|
||||
|
||||
const saved = savePostImportSession (sessionId, nextSession)
|
||||
const loadedSession = loadPostImportSession (sessionId)
|
||||
const canRecoverFromUrlOnly = serialised.complete
|
||||
if (!(canRecoverFromUrlOnly)
|
||||
&& (!(saved))
|
||||
&& !(serialisedPostImportRowsEqual (loadedSession?.rows ?? [], nextSession.rows)))
|
||||
return null
|
||||
if (saved
|
||||
&& !(serialisedPostImportRowsEqual (loadedSession?.rows ?? [], nextSession.rows)))
|
||||
return null
|
||||
const persistedSession = commitSessionState (nextSession)
|
||||
|
||||
const persistedSession = buildSession (nextSession.rows)
|
||||
const path = appendPostNewSessionId (serialised.path, sessionId)
|
||||
persistedSearchRef.current = (new URL (path, window.location.origin)).search
|
||||
sessionRef.current = persistedSession
|
||||
setSession (persistedSession)
|
||||
navigate (path, { replace: true })
|
||||
return persistedSession
|
||||
}
|
||||
|
||||
const currentRowBySource = (
|
||||
sourceRow: number,
|
||||
fallback?: PostImportSession,
|
||||
): PostImportRow | null =>
|
||||
(sessionRef.current?.rows ?? fallback?.rows ?? []).find (
|
||||
row => row.sourceRow === sourceRow) ?? null
|
||||
|
||||
const finishImport = () => {
|
||||
const sessionId = sessionIdRef.current
|
||||
if (sessionId != null)
|
||||
@@ -485,7 +532,9 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
const previewSequence = ++previewSequenceRef.current
|
||||
const controllers = new Set<AbortController> ()
|
||||
|
||||
if (sessionRef.current != null && persistedSearchRef.current === location.search)
|
||||
if (sessionRef.current != null
|
||||
&& persistedSearchRef.current === location.search
|
||||
&& !(sessionRef.current.rows.some (row => shouldFetchMetadata (row))))
|
||||
return () => {
|
||||
previewSequenceRef.current = previewSequence
|
||||
}
|
||||
@@ -496,20 +545,16 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
try
|
||||
{
|
||||
let nextIndex = 0
|
||||
const previewErrors = new Map<number, Record<string, string[]>> ()
|
||||
let workingRows = [...(sessionRef.current ?? baseSession).rows]
|
||||
|
||||
const mergeWorkingRow = (
|
||||
const mergeCurrentRow = (
|
||||
sourceRow: number,
|
||||
updater: (row: PostImportRow) => PostImportRow,
|
||||
) => {
|
||||
workingRows = workingRows.map (row =>
|
||||
const latestRows = sessionRef.current?.rows ?? baseSession.rows
|
||||
const nextRows = applyDuplicateUrlErrors (latestRows.map (row =>
|
||||
row.sourceRow === sourceRow
|
||||
? updater (row)
|
||||
: row)
|
||||
const nextSession = buildSession (workingRows)
|
||||
sessionRef.current = nextSession
|
||||
setSession (nextSession)
|
||||
: row))
|
||||
commitSessionState (buildSession (nextRows))
|
||||
}
|
||||
const worker = async () => {
|
||||
while (active)
|
||||
@@ -520,22 +565,25 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
return
|
||||
|
||||
const baseRow = baseSession.rows[index]
|
||||
if (baseRow.skipReason === 'manual'
|
||||
|| baseRow.importStatus === 'created'
|
||||
|| baseRow.importStatus === 'skipped'
|
||||
|| isNonRecoverableFailedRow (baseRow)
|
||||
|| baseRow.metadataUrl != null)
|
||||
const currentRow = currentRowBySource (baseRow.sourceRow, baseSession)
|
||||
if (currentRow == null || !(shouldFetchMetadata (currentRow)))
|
||||
continue
|
||||
const controller = new AbortController ()
|
||||
controllers.add (controller)
|
||||
try
|
||||
{
|
||||
const preview = await apiGet<PostMetadataResponse> ('/posts/metadata', {
|
||||
params: { url: baseRow.url },
|
||||
params: { url: currentRow.url },
|
||||
signal: controller.signal })
|
||||
if (!(active) || previewSequenceRef.current !== previewSequence)
|
||||
return
|
||||
mergeWorkingRow (baseRow.sourceRow, row => mergePreviewRow (row, preview))
|
||||
mergeCurrentRow (currentRow.sourceRow, row =>
|
||||
row.importStatus === 'created'
|
||||
|| row.importStatus === 'skipped'
|
||||
|| isNonRecoverableFailedRow (row)
|
||||
|| row.skipReason === 'manual'
|
||||
? row
|
||||
: mergePreviewRow (row, preview))
|
||||
}
|
||||
catch (requestError)
|
||||
{
|
||||
@@ -547,21 +595,29 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
errors?: Record<string, string[]>
|
||||
baseErrors?: string[]
|
||||
}> (requestError)))
|
||||
continue
|
||||
{
|
||||
mergeCurrentRow (currentRow.sourceRow, row => ({
|
||||
...row,
|
||||
status: 'error' }))
|
||||
continue
|
||||
}
|
||||
if (requestError.response?.status === 422)
|
||||
{
|
||||
const rowErrors = {
|
||||
const rowErrors = compactMessageRecord ({
|
||||
...(requestError.response.data.errors ?? { }),
|
||||
...(requestError.response.data.baseErrors?.length
|
||||
? {
|
||||
base: requestError.response.data.baseErrors }
|
||||
: { }) }
|
||||
previewErrors.set (baseRow.sourceRow, rowErrors)
|
||||
mergeWorkingRow (baseRow.sourceRow, row => ({
|
||||
: { }) })
|
||||
mergeCurrentRow (currentRow.sourceRow, row => ({
|
||||
...row,
|
||||
validationErrors: rowErrors,
|
||||
status: 'error' }))
|
||||
}
|
||||
else
|
||||
mergeCurrentRow (currentRow.sourceRow, row => ({
|
||||
...row,
|
||||
status: 'error' }))
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -575,20 +631,13 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
if (!(active) || previewSequenceRef.current !== previewSequence)
|
||||
return
|
||||
|
||||
const nextRows = applyDuplicateUrlErrors (workingRows.map (row => {
|
||||
const rowErrors = previewErrors.get (row.sourceRow)
|
||||
if (rowErrors == null)
|
||||
return row
|
||||
return {
|
||||
...row,
|
||||
validationErrors: rowErrors,
|
||||
status: 'error' as const }
|
||||
}))
|
||||
await persistSession (buildSession (nextRows))
|
||||
const latestRows = sessionRef.current?.rows ?? baseSession.rows
|
||||
const nextRows = applyDuplicateUrlErrors (latestRows)
|
||||
await syncSessionUrl (buildSession (nextRows))
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (active)
|
||||
if (active && previewSequenceRef.current === previewSequence)
|
||||
setMetadataLoading (false)
|
||||
}
|
||||
}
|
||||
@@ -649,18 +698,19 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
: rows
|
||||
const existingRows = sortedRows.filter (row => isExistingSkipRow (row))
|
||||
const reviewRows = sortedRows.filter (row => !(isExistingSkipRow (row)))
|
||||
const busy = metadataLoading || submitting || loadingRow != null
|
||||
const canSubmit =
|
||||
rows.every (row => isCompletedReviewRow (row))
|
||||
|| processableImportRows (rows).length > 0
|
||||
|
||||
const toggleManualSkip = async (sourceRow: number, checked: boolean) => {
|
||||
if (busy)
|
||||
return
|
||||
|
||||
const currentSession = sessionRef.current
|
||||
if (currentSession == null)
|
||||
return
|
||||
const currentRow = currentRowBySource (sourceRow, currentSession)
|
||||
if (currentRow == null)
|
||||
return
|
||||
if (rowSkipBusy (currentRow, submitting, loadingRow))
|
||||
return
|
||||
|
||||
const nextRows = currentSession.rows.map (row => {
|
||||
if (row.sourceRow !== sourceRow)
|
||||
@@ -674,7 +724,13 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
existingPostId: checked ? undefined : row.existingPostId,
|
||||
existingPost: checked ? undefined : row.existingPost }
|
||||
})
|
||||
await persistSession (buildSession (nextRows))
|
||||
const nextSession = buildSession (applyDuplicateUrlErrors (nextRows))
|
||||
if (metadataLoading)
|
||||
{
|
||||
commitSessionState (nextSession)
|
||||
return
|
||||
}
|
||||
await syncSessionUrl (nextSession)
|
||||
}
|
||||
|
||||
const saveDraft = async (
|
||||
@@ -687,23 +743,26 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
const currentSession = sessionRef.current
|
||||
if (currentSession == null)
|
||||
return { saved: false, row: null }
|
||||
if (!(canEditReviewRow (row)))
|
||||
const currentRow = currentRowBySource (row.sourceRow, currentSession)
|
||||
if (currentRow == null)
|
||||
return { saved: false, row: null }
|
||||
if (!(canEditReviewRow (currentRow)))
|
||||
return { saved: false, row: null }
|
||||
|
||||
const baseRow =
|
||||
resetRequested
|
||||
? { ...row,
|
||||
url: row.resetSnapshot.url,
|
||||
attributes: { ...row.resetSnapshot.attributes },
|
||||
provenance: { ...row.resetSnapshot.provenance },
|
||||
tagSources: { ...row.resetSnapshot.tagSources },
|
||||
? { ...currentRow,
|
||||
url: currentRow.resetSnapshot.url,
|
||||
attributes: { ...currentRow.resetSnapshot.attributes },
|
||||
provenance: { ...currentRow.resetSnapshot.provenance },
|
||||
tagSources: { ...currentRow.resetSnapshot.tagSources },
|
||||
fieldWarnings: Object.fromEntries (
|
||||
Object.entries (row.resetSnapshot.fieldWarnings)
|
||||
Object.entries (currentRow.resetSnapshot.fieldWarnings)
|
||||
.map (([key, values]) => [key, [...values]])),
|
||||
baseWarnings: [...row.resetSnapshot.baseWarnings],
|
||||
metadataUrl: row.resetSnapshot.metadataUrl,
|
||||
baseWarnings: [...currentRow.resetSnapshot.baseWarnings],
|
||||
metadataUrl: currentRow.resetSnapshot.metadataUrl,
|
||||
thumbnailFile: undefined }
|
||||
: row
|
||||
: currentRow
|
||||
const urlChanged = draft.url !== baseRow.url
|
||||
const nextRow = buildNextEditedRow (baseRow, draft, urlChanged)
|
||||
let candidateRow = nextRow
|
||||
@@ -724,15 +783,19 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
return { saved: false, row: null }
|
||||
|
||||
const mergedRow = mergeDryRunRow (candidateRow, dryRun)
|
||||
const mergedRows = replaceImportRow (latestSession.rows, mergedRow)
|
||||
const nextSession = await persistSession (buildSession (mergedRows))
|
||||
const mergedRows = applyDuplicateUrlErrors (
|
||||
replaceImportRow (latestSession.rows, mergedRow))
|
||||
const nextSession =
|
||||
metadataLoading
|
||||
? commitSessionState (buildSession (mergedRows))
|
||||
: await syncSessionUrl (buildSession (mergedRows))
|
||||
if (nextSession == null)
|
||||
return { saved: false, row: null }
|
||||
const mergedTarget = nextSession.rows.find (
|
||||
currentRow => currentRow.sourceRow === baseRow.sourceRow)
|
||||
if (mergedTarget == null)
|
||||
return { saved: false, row: null }
|
||||
if (Object.keys (mergedTarget.validationErrors).length > 0)
|
||||
if (hasErrorMessages (mergedTarget.validationErrors))
|
||||
return { saved: false, row: mergedTarget }
|
||||
return { saved: true, row: null }
|
||||
}
|
||||
@@ -746,7 +809,12 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
{
|
||||
const errorRow = {
|
||||
...candidateRow,
|
||||
validationErrors: requestError.response.data.errors ?? { },
|
||||
validationErrors: compactMessageRecord ({
|
||||
...(requestError.response.data.errors ?? { }),
|
||||
...(requestError.response.data.baseErrors?.length
|
||||
? {
|
||||
base: requestError.response.data.baseErrors }
|
||||
: { }) }),
|
||||
baseWarnings: [],
|
||||
fieldWarnings: { },
|
||||
status: 'error' as const }
|
||||
@@ -778,39 +846,48 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
}
|
||||
|
||||
const editRow = async (row: PostImportRow) => {
|
||||
setEditingRow (row)
|
||||
const currentRow = sessionRef.current?.rows.find (
|
||||
current => current.sourceRow === row.sourceRow)
|
||||
if (currentRow == null)
|
||||
return
|
||||
if (!(canEditReviewRow (currentRow)))
|
||||
return
|
||||
if (rowOperationBusy (currentRow, submitting, loadingRow))
|
||||
return
|
||||
|
||||
setEditingRow (currentRow)
|
||||
try
|
||||
{
|
||||
await openEditingDialogue (row)
|
||||
await openEditingDialogue (currentRow)
|
||||
}
|
||||
finally
|
||||
{
|
||||
setEditingRow (current =>
|
||||
current?.sourceRow === row.sourceRow
|
||||
current?.sourceRow === currentRow.sourceRow
|
||||
? null
|
||||
: current)
|
||||
}
|
||||
}
|
||||
|
||||
const retry = async (sourceRow: number) => {
|
||||
if (busy)
|
||||
return
|
||||
|
||||
const initialSession = sessionRef.current
|
||||
if (initialSession == null)
|
||||
return
|
||||
|
||||
setLoadingRow (sourceRow)
|
||||
const originalRow = initialSession.rows.find (row => row.sourceRow === sourceRow)
|
||||
if (originalRow == null)
|
||||
{
|
||||
setLoadingRow (null)
|
||||
return
|
||||
}
|
||||
return
|
||||
if (rowOperationBusy (originalRow, submitting, loadingRow))
|
||||
return
|
||||
|
||||
setLoadingRow (sourceRow)
|
||||
try
|
||||
{
|
||||
const pendingRows = retryImportRow (initialSession.rows, sourceRow)
|
||||
const pendingSession = await persistSession (buildSession (pendingRows))
|
||||
const pendingSession =
|
||||
metadataLoading
|
||||
? commitSessionState (buildSession (pendingRows))
|
||||
: await syncSessionUrl (buildSession (pendingRows))
|
||||
if (pendingSession == null)
|
||||
return
|
||||
|
||||
@@ -825,16 +902,20 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
{
|
||||
const latest = sessionRef.current ?? pendingSession
|
||||
const restoredRows = replaceImportRow (latest.rows, originalRow)
|
||||
await persistSession (buildSession (restoredRows))
|
||||
if (metadataLoading)
|
||||
commitSessionState (buildSession (restoredRows))
|
||||
else
|
||||
await syncSessionUrl (buildSession (restoredRows))
|
||||
toast ({ title: '登録結果が不完全でした' })
|
||||
return
|
||||
}
|
||||
|
||||
const indexedResults = indexedBulkResults ([target], result.results)
|
||||
const latestAfterImport = sessionRef.current ?? pendingSession
|
||||
const nextRows = mergeImportResults (latestAfterImport.rows, indexedResults)
|
||||
const nextRows = applyDuplicateUrlErrors (
|
||||
mergeImportResults (latestAfterImport.rows, indexedResults))
|
||||
.map (row => applyThumbnailWarning (row))
|
||||
const persisted = await persistSession (buildSession (nextRows))
|
||||
const persisted = await syncSessionUrl (buildSession (nextRows))
|
||||
if (persisted != null
|
||||
&& persisted.rows.every (row => isCompletedReviewRow (row)))
|
||||
finishImport ()
|
||||
@@ -843,7 +924,10 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
{
|
||||
const latest = sessionRef.current ?? initialSession
|
||||
const restoredRows = replaceImportRow (latest.rows, originalRow)
|
||||
await persistSession (buildSession (restoredRows))
|
||||
if (metadataLoading)
|
||||
commitSessionState (buildSession (restoredRows))
|
||||
else
|
||||
await syncSessionUrl (buildSession (restoredRows))
|
||||
toast ({ title: '再試行に失敗しました' })
|
||||
}
|
||||
finally
|
||||
@@ -854,7 +938,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
|
||||
const submit = async () => {
|
||||
const currentSession = sessionRef.current
|
||||
if (currentSession == null || busy)
|
||||
if (currentSession == null || metadataLoading || submitting || loadingRow != null)
|
||||
return
|
||||
if (currentSession.rows.every (row => isCompletedReviewRow (row)))
|
||||
{
|
||||
@@ -880,9 +964,10 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
|
||||
const indexedResults = indexedBulkResults (processingRows, result.results)
|
||||
const latestAfterImport = sessionRef.current ?? buildSession (currentSession.rows)
|
||||
const nextRows = mergeImportResults (latestAfterImport.rows, indexedResults)
|
||||
const nextRows = applyDuplicateUrlErrors (
|
||||
mergeImportResults (latestAfterImport.rows, indexedResults))
|
||||
.map (row => applyThumbnailWarning (row))
|
||||
const persisted = await persistSession (buildSession (nextRows))
|
||||
const persisted = await syncSessionUrl (buildSession (nextRows))
|
||||
if (persisted != null
|
||||
&& persisted.rows.every (row => isCompletedReviewRow (row)))
|
||||
finishImport ()
|
||||
@@ -958,10 +1043,10 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
||||
row={row}
|
||||
displayNumber={index + 1}
|
||||
showSkipToggle={true}
|
||||
editDisabled={busy}
|
||||
retryDisabled={busy}
|
||||
editDisabled={rowOperationBusy (row, submitting, loadingRow)}
|
||||
retryDisabled={rowOperationBusy (row, submitting, loadingRow)}
|
||||
skipDisabled={
|
||||
busy
|
||||
rowSkipBusy (row, submitting, loadingRow)
|
||||
|| isExistingSkipRow (row)
|
||||
|| row.importStatus === 'created'}
|
||||
onEdit={() => editRow (row)}
|
||||
|
||||
新しい課題から参照
ユーザをブロックする