このコミットが含まれているのは:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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 ('')
|
||||
|
||||
@@ -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 }
|
||||
|
||||
新しいイシューから参照
ユーザーをブロックする