このコミットが含まれているのは:
2026-10-31 04:08:05 +09:00
コミット 6a0f1ec819
23個のファイルの変更、188行の追加、57行の削除
+1 -1
ファイルの表示
@@ -51,7 +51,7 @@ const CountsAxes = ({ params, update }: {
return <SortableList groups={groups} name={name} onChange={commit}
renderGroup={(id, content, over) => <fieldset className="mb-2 min-w-0">
<legend>{label (id === 'available' ? 'axisItems' : id)}</legend>
<div className={cn ('rounded border dark:border-slate-700',
<div className={cn ('rounded border px-1 dark:border-slate-700',
over && 'ring-1 ring-blue-500 dark:ring-blue-400')}>{content}</div>
</fieldset>} renderItem={(id, group) => {
const axis = axes.find (axis => axis.field === id)
+3 -3
ファイルの表示
@@ -185,16 +185,16 @@ const CountsControls = (
className={inputClass (false, 'w-20 py-1')}
onChange={event => update ('limit', event.target.value)}>
{[20, 50, 100].map (count => <option key={count} value={count}>
{label ('shared:labels.resultsCount', { count })}</option>)}
{message ('shared:labels.resultsCount', { count }).text}</option>)}
</select>
</label>
<label className="inline-flex items-center gap-1 text-sm">
{details && <label className="inline-flex items-center gap-1 text-sm">
<input type="checkbox" checked={draft.get ('row_numbers') === '1'}
onChange={event => {
update ('row_numbers', event.target.checked ? '1' : '0')
displayRowNumbers (event.target.checked)
}}/>{label ('rowNumbers')}
</label>
</label>}
{details && resource === 'posts' && <label
className="inline-flex items-center gap-1 text-sm">
<input type="checkbox" checked={thumbnail}
+20 -8
ファイルの表示
@@ -32,8 +32,10 @@ const CountsExportProgress = (
const job = useRef<string | null> (null)
const creation = useRef<Promise<{ job: string }> | null> (null)
const downloaded = useRef (false)
const registration = useRef (false)
const attempt = useRef<string | null> (null)
const downloadDeadline = useRef (0)
const acknowledgementTimer = useRef<ReturnType<typeof setTimeout> | null> (null)
const releaseDownload = useRef<(() => void) | null> (null)
const [downloadError, setDownloadError] = useState<string | null> (null)
const pollRequest = useRef<AbortController | null> (null)
@@ -79,25 +81,33 @@ const CountsExportProgress = (
const response = await apiGet<ExportStatus> (
`/counts/export/jobs/${encodeURIComponent (token)}`, {
locale, signal: controller.signal })
if (!active) return
if (!active || terminal.current) return
const result = (cancelled.current && response.status === 'ready')
? await apiDelete<ExportStatus> (
`/counts/export/jobs/${encodeURIComponent (token)}`, {
locale, signal: controller.signal }) : response
if (!active) return
if (!active || terminal.current) return
lastResponse = Date.now ()
setState (result)
terminal.current = ['failed', 'cancelled'].includes (result.status)
if (result.status === 'ready' && result.token != null && !downloaded.current)
{
downloaded.current = true
registration.current = true
const receipt = await apiPost<ExportStatus> (
`/counts/export/jobs/${encodeURIComponent (token)}/download`, {}, {
locale, signal: controller.signal })
if (!active || cancelled.current) return
if (receipt.downloadAttempt == null) throw new Error ('Missing download attempt')
downloaded.current = true
attempt.current = receipt.downloadAttempt
downloadDeadline.current = Date.now () + 30_000
acknowledgementTimer.current = setTimeout (() => {
if (!active || terminal.current) return
terminal.current = true
pollRequest.current?.abort ()
releaseDownload.current?.()
setDownloadError ('timeout')
}, 30_000)
releaseDownload.current = startAPIDownload ('/counts/export', result.token, () => {
if (active)
{
@@ -132,13 +142,13 @@ const CountsExportProgress = (
}
catch (error)
{
if (active)
if (active && !terminal.current)
{
if ((isApiError (error) && error.response?.status === 404)
if (downloaded.current || registration.current || (isApiError (error) && error.response?.status === 404)
|| Date.now () - lastResponse > 30_000)
{
terminal.current = true
if (downloaded.current) setDownloadError ('unavailable')
setDownloadError ('unavailable')
setState (previous => ({ ...previous, status: 'failed' }))
return
}
@@ -160,13 +170,14 @@ const CountsExportProgress = (
locale, signal: controller.signal }).finally (() => clearTimeout (deadline))
}
creation.current.then (async result => {
if (!active) return
if (!active || terminal.current) return
job.current = result.job
if (cancelled.current) await handle.cancel ()
await poll (result.job)
}).catch (() => {
if (active)
{
setDownloadError ('failed')
terminal.current = true
setState (previous => ({ ...previous, status: 'failed' }))
}
@@ -176,8 +187,9 @@ const CountsExportProgress = (
if (timer != null) clearTimeout (timer)
pollRequest.current?.abort ()
releaseDownload.current?.()
if (acknowledgementTimer.current != null) clearTimeout (acknowledgementTimer.current)
}
}, [handle, locale, payload, controls])
}, [handle, locale])
useEffect (() => {
controls.setActions ([])
}, [controls])
+5 -1
ファイルの表示
@@ -3,6 +3,7 @@ import { useContext, useEffect, useRef } from 'react'
import SortableItem from '@/components/common/SortableItem'
import CountsResizeHandle from '@/components/counts/CountsResizeHandle'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
import { useUITranslation } from '@/lib/useUITranslation'
import { CountsTableScope, useCountsTableLayout } from '@/lib/countsTableLayout'
@@ -129,7 +130,10 @@ const CountsSortHeader = (
}
change (field, event.ctrlKey || event.metaKey)
}}>
<span data-counts-label className="truncate" lang={label.language}>{label.text}</span>
<span data-counts-label className={cn ('truncate', label.text === '********'
&& 'text-red-600 dark:text-red-400')}
title={label.text === '********' ? message ('maskedReason').text : undefined}
lang={label.language}>{label.text}</span>
<span aria-hidden data-counts-sort-indicator className="inline-block w-10 shrink-0">
{priority >= 0 && <>{direction === 'asc' ? '▲' : '▼'}
{sorts.length > 1 && priority + 1}</>}</span>
+7 -4
ファイルの表示
@@ -26,7 +26,7 @@ const CountsSummaryTable = (
const definition = useCountsDefinition ()
const locale = useLocale ()
const { message } = useUITranslation ('counts')
const numbered = params.get ('row_numbers') === '1'
const numbered = false
const rows = data.rows ?? []
const cols = data.cols ?? []
const cells = data.cells ?? []
@@ -260,7 +260,9 @@ const CountsSummaryTable = (
data-counts-total={row.kind !== 'data' ? row.kind : undefined}
data-counts-row-total={row.kind !== 'data' ? row.kind : undefined}
className={cn ('overflow-hidden break-words border px-2 py-1 dark:border-slate-700',
numbered && offset === 0 && 'text-right tabular-nums')}>{value}</td>
numbered && offset === 0 && 'text-right tabular-nums')}>{value === '********' ? <span
className="text-red-600 dark:text-red-400"
title={message ('maskedReason').text}>{value}</span> : value}</td>
})}
{columnHeaders.map (column => <td key={columnKey (column)}
data-counts-copy={String (count (row, column))}
@@ -271,11 +273,12 @@ const CountsSummaryTable = (
data-counts-aggregate={(row.kind !== 'data' || column.kind !== 'data')
? '' : undefined}
className="border px-2 py-1 dark:border-slate-700">
<PrefetchLink to={drill (row, column)} aria-label={
{[...row.values, ...column.values].includes ('__counts_private__')
? <span>{count (row, column)}</span> : <PrefetchLink to={drill (row, column)} aria-label={
totalLabels.get (JSON.stringify ([row.values, column.values])) == null ? undefined
: `${message (totalLabels.get (
JSON.stringify ([row.values, column.values]))!).text}: ${count (row, column)}`}>
{count (row, column)}</PrefetchLink>
{count (row, column)}</PrefetchLink>}
</td>)}
</tr>)}</tbody>
</CountsSelectableTable>