ファイル
btrc-hub/frontend/src/lib/countsDefinitions.ts
T
2026-10-26 19:16:19 +09:00

90 行
3.2 KiB
TypeScript

import { createContext, useContext } from 'react'
import { translateUIMessage } from '@/lib/i18n'
import { apiGet } from '@/lib/api'
import type { LocaleCode } from '@/lib/locales'
import type { CountField, CountsResource } from '@/lib/counts'
export type CountsAxis = { field: string; grain?: string }
export type CountsCondition = {
field: string; operator: string; value?: string; end?: string; blank?: boolean }
export type CountsFieldType = 'string' | 'integer' | 'datetime' | 'enum' | 'json' | 'boolean'
export const countsResources: CountsResource[] = [
'posts', 'tags', 'nico_tags', 'tag_descriptions', 'tag_groups', 'materials']
export type CountsFieldMetadata = {
key: string; type: CountsFieldType; nullable: boolean; operators: string[] }
export type CountsDefinition = {
fields: CountsFieldMetadata[]
axes: { latest: string[]; history: string[] }
grains: string[] }
export const CountsDefinitionContext = createContext<CountsDefinition | null> (null)
export const useCountsDefinition = (): CountsDefinition => {
const definition = useContext (CountsDefinitionContext)
if (definition == null) throw new Error ('Missing Counts definition')
return definition
}
export const fetchCountsDefinition = (resource: CountsResource): Promise<CountsDefinition> =>
apiGet (`/counts/definitions/${encodeURIComponent (resource)}`)
export const countsFilterFields = (
definition: CountsDefinition,
): Record<string, CountsFieldType> => Object.fromEntries (
definition.fields.map (field => [field.key, field.type]))
export const countsFieldLabel = (key: string): string => {
const labels: Record<string, string> = {
resource_id: 'columns.resourceId', version_no: 'history:version',
user: 'history:actor', created_at: 'columns.changedAt', name: 'history:name',
title: 'columns.postTitle', url: 'columns.postURL', thumbnail_base: 'columns.thumbnailURL',
tags: 'columns.tags', tags_json: 'columns.tags', names: 'columns.names',
aliases: 'tags:aliases', category: 'common:categoryLabel',
language_code: 'columns.language', source_language_code: 'attributes.sourceLanguage',
changed_field: 'changedField' }
return labels[key] ?? `attributes.${key}`
}
export const countsEventTypes = ['create', 'update', 'discard', 'restore'] as const
export const countsAxisLabel = (
axis: CountsAxis,
definition: CountsDefinition,
locale: LocaleCode,
): { text: string; language: string } => {
const field = translateUIMessage (countsFieldLabel (axis.field), 'counts', locale)
if (axis.grain == null || definition.fields.find (item => item.key === axis.field)
?.type !== 'datetime') return field
return translateUIMessage ('axisWithGrain', 'counts', locale, {
field: field.text,
grain: translateUIMessage (`grains.${axis.grain}`, 'counts', locale).text })
}
export const countsAxisFields = (
definition: CountsDefinition,
history: boolean,
): CountField[] => definition.axes[history ? 'history' : 'latest']
.map (key => ({ key, label: countsFieldLabel (key) }))
export const readCountsJSON = <T,> (params: URLSearchParams, key: string, fallback: T): T => {
try
{
return JSON.parse (params.get (key) ?? JSON.stringify (fallback)) as T
}
catch
{
return fallback
}
}