このコミットが含まれているのは:
2026-10-28 19:28:11 +09:00
コミット 8ea3d211dc
49個のファイルの変更、967行の追加、356行の削除
+37 -7
ファイルの表示
@@ -1,5 +1,6 @@
import { countsFieldLabel } from '@/lib/countsDefinitions'
import { apiDownload, apiGet } from '@/lib/api'
import { currentViewingLocale } from '@/lib/locales'
import { readCountState, encodeCountState } from '@/lib/queryState'
@@ -61,7 +62,8 @@ export type CountsPivotHeader = { values: (string | number | null)[]
kind: 'data' | 'subtotal' | 'grand_total'
depth?: number; axisCount?: number; labelKey?: string | null }
export type CountsResult = {
populationCount?: number
populationCount?: number | null
populationCountComputed?: boolean
excludedCount?: number | null
excludedCountScope?: string
outputCount?: number | null
@@ -76,6 +78,7 @@ export type CountsResult = {
columnValues?: (string | number | null)[][]
cells?: { values: (string | number | null)[]; count: number }[]
axisLabels?: Record<string, Record<string, string | null>>
axisNames?: Record<string, Record<string, Record<string, unknown>>>
rows?: { field: string; grain?: string }[]
cols?: { field: string; grain?: string }[]
count: number
@@ -93,7 +96,9 @@ export type CountsResult = {
coverage?: { basis: string; exact: boolean
postHistoryFrom: string | null; tagHistoryFrom: string | null } }
export type CountSort = { key: string; direction: 'asc' | 'desc' }
export type CountField = { key: string; label: string; children?: CountField[] }
export type CountField = { key: string; label: string; children?: CountField[]
labelValues?: Record<string, string>
nameLocale?: { code: string; languageCode: string; scriptCode: string } }
export type CountRecord = Record<string, unknown>
export const countColumns: Record<string, string> = {
@@ -177,7 +182,8 @@ export const fieldsForCounts = (
label: (key === 'parents' && resource === 'tags') ? 'tags:parents'
: (countColumns[key] ?? countsFieldLabel (key)),
children: key === 'parents' && resource === 'tags'
? [...(nested.parents ?? []), { key: 'name', label: 'counts:columns.name' }]
? [...(nested.parents ?? []), { key: 'name', label: 'counts:columns.name' },
{ key: 'names', label: 'counts:columns.names', children: nameFields }]
: nested[key] }))
@@ -327,6 +333,29 @@ export const normaliseCountsParams = (
definition?: CountsDefinition,
): URLSearchParams => {
const params = new URLSearchParams (source)
if (definition?.nameColumns != null)
{
const names = definition.nameColumns.map (field => field.key)
const current = definition.nameColumns.find (field =>
field.nameLocale?.code === currentViewingLocale ())?.key
if (resource === 'tags' && params.has ('columns'))
params.set ('columns', params.get ('columns')!.split (' ').flatMap (key =>
key === 'names' ? names : [key === 'name' && current != null ? current : key])
.join (' '))
if (params.has ('children'))
{
const selected = params.get ('children')!.split (' ')
params.set ('children', selected.flatMap (key => {
if (['tags.names', 'parents.names'].includes (key))
return names.map (name => `${key.slice (0, -6)}.${name}`)
return key.startsWith ('tags.names.') || key.startsWith ('parents.names.') ? [] : [key]
}).join (' '))
}
if (resource === 'tags' && params.has ('order'))
params.set ('order', params.get ('order')!.split (' ').flatMap (key =>
key === 'names' ? names : [key === 'name' && current != null ? current : key])
.join (' '))
}
for (const [key, value] of params)
{
if (['columns', 'order', 'sort', 'rows', 'cols', 'axis'].includes (key))
@@ -353,11 +382,11 @@ export const normaliseCountsParams = (
params.set ('through', params.get ('before')!)
for (const key of ['basis', 'axis', 'latest', 'grain', 'before', 'summary_columns'])
params.delete (key)
if (params.has ('columns') && ['posts', 'tags'].includes (resource))
if (params.has ('columns') && definition != null && ['posts', 'tags'].includes (resource))
{
const selected = new Set (params.get ('columns')!.split (' '))
params.set ('columns', columnsForCounts (resource).filter (key => selected.has (key))
.join (' '))
params.set ('columns', columnsForCounts (resource, definition)
.filter (key => selected.has (key)).join (' '))
}
if (params.has ('sort'))
params.set ('sort', params.get ('sort')!.split (' ')
@@ -374,7 +403,8 @@ export const normaliseCountsParams = (
{
const expected = defaults?.params.find (entry => entry.key === 'rows')?.value
const axes = readCountState<CountsAxis[]> (params, 'rows', [])
const first = expected == null ? null : readCountState<CountsAxis[]> (new URLSearchParams ({ rows: expected }), 'rows', [])[0]
const first = expected == null ? null : readCountState<CountsAxis[]> (
new URLSearchParams ({ rows: expected }), 'rows', [])[0]
if (!Array.isArray (axes) || axes[0]?.field !== first?.field
|| (axes[0]?.grain ?? 'month') !== (first?.grain ?? 'month')) continue
}
+7 -2
ファイルの表示
@@ -25,10 +25,12 @@ export type CountsFieldMetadata = {
key: string; type: CountsFieldType; nullable: boolean; operators: string[]
labels?: string[]; multiple?: boolean; candidateSource?: 'categories' | 'tags'
setType?: 'tag_reference'; inputTypes?: string[]
labelValues?: Record<string, string>
choices?: { value: string; label: string }[] }
export type CountsDefinition = {
fields: CountsFieldMetadata[]
outputFields?: CountField[]
nameColumns?: CountField[]
filterFields?: CountsFieldMetadata[]
defaults?: { definition: string; mode: string; params: { key: string; value: string }[] }[]
axes: { latest: string[]; history: string[] }
@@ -44,11 +46,14 @@ export const useCountsDefinition = (): CountsDefinition => {
}
export const fetchCountsDefinition = (resource: CountsResource): Promise<CountsDefinition> =>
apiGet (`/counts/definitions/${encodeURIComponent (resource)}`)
export const fetchCountsDefinition = (
resource: CountsResource, locale: LocaleCode = 'ja',
): Promise<CountsDefinition> => apiGet (
`/counts/definitions/${encodeURIComponent (resource)}`, { locale })
export type CountsCandidate = {
value: string; id?: number; name: string | null; category?: string | null
names?: Record<string, unknown>
kind?: 'internal' | 'external' }
export type CountsCandidatesResult = {
rows: CountsCandidate[]; page: number; more: boolean; executionAt: string }
+17 -15
ファイルの表示
@@ -12,6 +12,10 @@ import type { CountsAxis, CountsDefinition } from '@/lib/countsDefinitions'
import type { LocaleCode } from '@/lib/locales'
export const countsFieldValue = (row: CountRecord, field: string): unknown =>
row[field] ?? row[field.replace (/_([a-z])/g, (_, letter: string) => letter.toUpperCase ())]
export const countsCellText = (
row: CountsVersion,
column: CountColumn,
@@ -85,8 +89,8 @@ export const countsCSVCell = (
params: URLSearchParams,
locale: LocaleCode,
): string => {
const field = fieldsForCounts (resource).find (field => field.key === column)!
if (field.children != null)
const field = fieldsForCounts (resource).find (field => field.key === column)
if (field?.children != null)
{
const rows = countsNestedRows (row, column)
return JSON.stringify (countsNestedExport (rows, field.children, column, params, locale))
@@ -117,7 +121,7 @@ export const countsLogicalName = (
field: string,
locale: LocaleCode,
): { text: string; language?: string } => {
const value = row[field]
const value = countsFieldValue (row, field)
if (value == null) return { text: '' }
if (field === 'sections')
return { text: (countsSectionRanges (value, locale) ?? []).join (
@@ -158,10 +162,8 @@ export const countsNestedOrder = (
rows: CountRecord[],
path: string,
params: URLSearchParams,
locale: LocaleCode,
): CountRecord[] => sortedCountChildren (rows, path, params, (row, key) =>
['languageCode', 'scriptCode'].includes (key)
? countsLogicalName (row, key, locale).text : row[key])
_locale: LocaleCode,
): CountRecord[] => sortedCountChildren (rows, path, params, countsFieldValue)
export const countsNestedExport = (
@@ -177,13 +179,11 @@ export const countsNestedExport = (
if (columns.length === 0) return rows.map (() => ({ }))
return countsNestedOrder (rows, path, params, locale).map (row =>
Object.fromEntries (columns.map (field => [
translateUIMessage (field.label, 'counts', locale).text,
field.key,
field.children
? countsNestedExport (row[field.key] as CountRecord[] ?? [], field.children,
`${path}.${field.key}`, params, locale)
: (field.key === 'sections'
? countsSectionRanges (row[field.key], locale)
: countsLogicalName (row, field.key, locale).text)])))
: countsFieldValue (row, field.key)])))
}
@@ -238,7 +238,8 @@ export const countsNestedText = (
? countsNestedText (row[field.key] as CountRecord[] ?? [], field.children,
`${path}.${field.key}`, params, locale)
: countsLogicalName (row, field.key, locale).text
return `${translateUIMessage (field.label, 'counts', locale).text}: ${value}`
const label = translateUIMessage (field.label, 'counts', locale, field.labelValues).text
return `${label}: ${value}`
}).join (' | ')).join ('\n')
}
@@ -288,7 +289,7 @@ export const countsExportLabels = (
for (const field of fields)
{
labels[aliases[field.key] ?? field.key] = translateUIMessage (
field.label, 'counts', locale).text
field.label, 'counts', locale, field.labelValues).text
if (field.children) visit (field.children)
}
}
@@ -328,7 +329,8 @@ const clipboardColumns = (
const selected = selectedCountChildren (params)
return orderedCountFields (fields, params, path)
.filter (field => selected.has (`${path}.${field.key}`))
.map (field => ({ label: translateUIMessage (field.label, 'counts', locale).text,
.map (field => ({ label: translateUIMessage (
field.label, 'counts', locale, field.labelValues).text,
children: (field.children == null
|| countsNestedIsScalar (field.children, `${path}.${field.key}`, params))
? undefined : clipboardColumns (
@@ -366,7 +368,7 @@ export const countsDetailClipboardTable = (
rowStart: number | null = null,
): CountsClipboardTable => {
const columns = fields.map (field => ({
label: translateUIMessage (field.label, 'counts', locale).text,
label: translateUIMessage (field.label, 'counts', locale, field.labelValues).text,
children: (field.children == null
|| countsNestedIsScalar (field.children, field.key, params))
? undefined : clipboardColumns (
+1
ファイルの表示
@@ -14,6 +14,7 @@ export const defaultCountWidths: Record<string, number> = {
type Layout = {
width: (family: string, key: string) => number
custom: (family: string, key: string) => boolean
resize: (family: string, key: string, width: number) => void }
export const CountsLayoutContext = createContext<Layout | null> (null)
export const CountsTableScope = createContext ('')
+2 -1
ファイルの表示
@@ -172,7 +172,8 @@ export const theatresKeys = {
['theatres', locale, id, 'programmes'] as const }
export const countsKeys = {
definition: (resource: string) => ['counts', 'definition', resource] as const,
definition: (resource: string, locale: LocaleCode = 'ja') =>
['counts', 'definition', resource, locale] as const,
candidates: (params: string, locale: LocaleCode) => ['counts', 'candidates', locale, params] as const,
index: (resource: string, params: string, locale: LocaleCode) =>
['counts', resource, locale, params] as const }