このコミットが含まれているのは:
@@ -14,12 +14,13 @@ import type { CountsResource } from '@/lib/counts'
|
|||||||
|
|
||||||
|
|
||||||
const CountsControls = (
|
const CountsControls = (
|
||||||
{ resource, applied, open, setOpen, execute }: {
|
{ resource, applied, open, setOpen, execute, displayJSON }: {
|
||||||
resource: CountsResource
|
resource: CountsResource
|
||||||
applied: URLSearchParams
|
applied: URLSearchParams
|
||||||
open: boolean
|
open: boolean
|
||||||
setOpen: (open: boolean) => void
|
setOpen: (open: boolean) => void
|
||||||
execute: (params: URLSearchParams) => void },
|
execute: (params: URLSearchParams) => void
|
||||||
|
displayJSON: (enabled: boolean) => void },
|
||||||
) => {
|
) => {
|
||||||
const { label, message } = useUITranslation ('counts')
|
const { label, message } = useUITranslation ('counts')
|
||||||
const locale = useLocale ()
|
const locale = useLocale ()
|
||||||
@@ -92,6 +93,14 @@ const CountsControls = (
|
|||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<fieldset className="min-w-0" data-counts-output>
|
<fieldset className="min-w-0" data-counts-output>
|
||||||
<legend className="mb-2 font-semibold">{label ('output')}</legend>
|
<legend className="mb-2 font-semibold">{label ('output')}</legend>
|
||||||
|
<label className="mb-2 flex items-center gap-1">
|
||||||
|
<input type="checkbox" checked={draft.get ('nested_json') === '1'}
|
||||||
|
onChange={event => {
|
||||||
|
update ('nested_json', event.target.checked ? '1' : '0')
|
||||||
|
displayJSON (event.target.checked)
|
||||||
|
}}/>
|
||||||
|
{label ('nested.asJSON')}
|
||||||
|
</label>
|
||||||
{mode === 'summary' ? (
|
{mode === 'summary' ? (
|
||||||
<label className="flex flex-wrap items-center gap-2">
|
<label className="flex flex-wrap items-center gap-2">
|
||||||
{label ('axis')}
|
{label ('axis')}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import CountsSortHeader from '@/components/counts/CountsSortHeader'
|
|||||||
import CountsSelectableTable from '@/components/counts/CountsSelectableTable'
|
import CountsSelectableTable from '@/components/counts/CountsSelectableTable'
|
||||||
import { changeCountSort, readCountSort, selectedCountChildren,
|
import { changeCountSort, readCountSort, selectedCountChildren,
|
||||||
writeCountSort } from '@/lib/counts'
|
writeCountSort } from '@/lib/counts'
|
||||||
import { countsLogicalName, countsNestedOrder, countsNestedText } from '@/lib/countsDisplay'
|
import { countsLogicalName, countsNestedExport,
|
||||||
|
countsNestedOrder, countsNestedText } from '@/lib/countsDisplay'
|
||||||
import { useLocale } from '@/lib/useLocale'
|
import { useLocale } from '@/lib/useLocale'
|
||||||
import { useUITranslation } from '@/lib/useUITranslation'
|
import { useUITranslation } from '@/lib/useUITranslation'
|
||||||
|
|
||||||
@@ -53,8 +54,14 @@ const CountsNestedTable = (
|
|||||||
<summary className="cursor-pointer text-xs" title={message ('nested.toggle').text}>
|
<summary className="cursor-pointer text-xs" title={message ('nested.toggle').text}>
|
||||||
<LocalisedText language={title.language}>{title.text}</LocalisedText> ({rows.length})
|
<LocalisedText language={title.language}>{title.text}</LocalisedText> ({rows.length})
|
||||||
</summary>
|
</summary>
|
||||||
{columns.length > 0 && rows.length > 0 && (
|
{params.get ('nested_json') === '1' ? (
|
||||||
<div className="relative isolate mt-1 max-h-72 w-full min-w-0 overflow-auto">
|
<pre data-counts-scroll
|
||||||
|
className="mt-1 max-h-72 w-full overflow-auto whitespace-pre-wrap
|
||||||
|
break-words text-xs">
|
||||||
|
{JSON.stringify (countsNestedExport (rows, fields, path, params, locale), null, 2)}
|
||||||
|
</pre>) : (columns.length > 0 && rows.length > 0 && (
|
||||||
|
<div data-counts-scroll
|
||||||
|
className="relative isolate mt-1 max-h-72 w-full min-w-0 overflow-auto">
|
||||||
<CountsSelectableTable family={path} columns={columns.map (field => field.key)}
|
<CountsSelectableTable family={path} columns={columns.map (field => field.key)}
|
||||||
identity={`${path}:${params}`} name={title}
|
identity={`${path}:${params}`} name={title}
|
||||||
className="border text-xs dark:border-slate-700">
|
className="border text-xs dark:border-slate-700">
|
||||||
@@ -93,7 +100,7 @@ const CountsNestedTable = (
|
|||||||
</td>))}
|
</td>))}
|
||||||
</tr>))}</tbody>
|
</tr>))}</tbody>
|
||||||
</CountsSelectableTable>
|
</CountsSelectableTable>
|
||||||
</div>)}
|
</div>))}
|
||||||
</details>)
|
</details>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import CountsSortHeader from '@/components/counts/CountsSortHeader'
|
|||||||
import CountsResizeHandle from '@/components/counts/CountsResizeHandle'
|
import CountsResizeHandle from '@/components/counts/CountsResizeHandle'
|
||||||
import CountsSelectableTable from '@/components/counts/CountsSelectableTable'
|
import CountsSelectableTable from '@/components/counts/CountsSelectableTable'
|
||||||
import PostThumbnailPreview from '@/components/posts/PostThumbnailPreview'
|
import PostThumbnailPreview from '@/components/posts/PostThumbnailPreview'
|
||||||
import { changeCountSort, countColumns, fieldsForCounts,
|
import { changeCountSort, countColumns, countsRequestParams, fieldsForCounts,
|
||||||
readCountSort, selectedCountColumns, writeCountSort } from '@/lib/counts'
|
readCountSort, selectedCountColumns, writeCountSort } from '@/lib/counts'
|
||||||
import { countsCellCopy, countsCellText, countsImageURL } from '@/lib/countsDisplay'
|
import { countsCellCopy, countsCellText, countsImageURL } from '@/lib/countsDisplay'
|
||||||
import { useLocale } from '@/lib/useLocale'
|
import { useLocale } from '@/lib/useLocale'
|
||||||
@@ -78,8 +78,8 @@ const CountsResults = (
|
|||||||
return <a href={countsImageURL (value) ?? undefined} target="_blank" rel="noreferrer">
|
return <a href={countsImageURL (value) ?? undefined} target="_blank" rel="noreferrer">
|
||||||
{value}</a>
|
{value}</a>
|
||||||
case 'is_latest':
|
case 'is_latest':
|
||||||
return <span aria-label={message (`boolean.${row.isLatest}`).text}
|
return <LocalisedText language={message ('latestValue').language}>
|
||||||
lang={message (`boolean.${row.isLatest}`).language}>{row.isLatest ? '✓' : '✗'}</span>
|
{value}</LocalisedText>
|
||||||
case 'user':
|
case 'user':
|
||||||
return row.createdByUser?.name ? value : (
|
return row.createdByUser?.name ? value : (
|
||||||
<LocalisedText language={message (
|
<LocalisedText language={message (
|
||||||
@@ -96,7 +96,8 @@ const CountsResults = (
|
|||||||
{!summary && columns.length === 0 ? <p>{label ('nested.noColumns')}</p> : (
|
{!summary && columns.length === 0 ? <p>{label ('nested.noColumns')}</p> : (
|
||||||
<CountsSelectableTable family={summary ? 'summary' : resource}
|
<CountsSelectableTable family={summary ? 'summary' : resource}
|
||||||
columns={summary ? ['group', 'count'] : columns}
|
columns={summary ? ['group', 'count'] : columns}
|
||||||
identity={`${resource}:${params}:${(data.versions ?? []).map (row => row.id)}`}
|
identity={`${resource}:${countsRequestParams (params)}:${columns.join (',')}:${
|
||||||
|
(data.versions ?? []).map (row => row.id)}`}
|
||||||
className="text-left text-sm">
|
className="text-left text-sm">
|
||||||
<thead className="sticky top-0 z-20 bg-background text-foreground
|
<thead className="sticky top-0 z-20 bg-background text-foreground
|
||||||
dark:bg-background dark:text-foreground"><tr>
|
dark:bg-background dark:text-foreground"><tr>
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ const CountsSelectableTable = (
|
|||||||
aria-activedescendant={selection == null ? undefined
|
aria-activedescendant={selection == null ? undefined
|
||||||
: `${id}-${selection.end.row}-${selection.end.column}`}
|
: `${id}-${selection.end.row}-${selection.end.column}`}
|
||||||
style={{ tableLayout: 'fixed', width: totalWidth }}
|
style={{ tableLayout: 'fixed', width: totalWidth }}
|
||||||
className={cn ('select-none border-separate border-spacing-0 outline-none',
|
className={cn ('counts-grid select-none border-separate border-spacing-0 outline-none',
|
||||||
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-500',
|
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-500',
|
||||||
'dark:focus-visible:outline-blue-400',
|
'dark:focus-visible:outline-blue-400',
|
||||||
'[&>tbody>tr>td[data-counts-selected]]:bg-blue-100',
|
'[&>tbody>tr>td[data-counts-selected]]:bg-blue-100',
|
||||||
@@ -144,6 +144,12 @@ const CountsSelectableTable = (
|
|||||||
const element = event.target as Element
|
const element = event.target as Element
|
||||||
if (element.closest ('table') !== event.currentTarget
|
if (element.closest ('table') !== event.currentTarget
|
||||||
|| element.closest (interactive) != null || event.button !== 0) return
|
|| element.closest (interactive) != null || event.button !== 0) return
|
||||||
|
if (element instanceof HTMLElement && element.hasAttribute ('data-counts-scroll'))
|
||||||
|
{
|
||||||
|
const rect = element.getBoundingClientRect ()
|
||||||
|
if (event.clientX >= rect.left + element.clientLeft + element.clientWidth
|
||||||
|
|| event.clientY >= rect.top + element.clientTop + element.clientHeight) return
|
||||||
|
}
|
||||||
const point = pointFor (element)
|
const point = pointFor (element)
|
||||||
if (point == null) return
|
if (point == null) return
|
||||||
event.stopPropagation ()
|
event.stopPropagation ()
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
.counts-grid > tbody > tr > td {
|
||||||
|
@apply h-6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counts-grid > tbody > tr:nth-child(odd) > td:not([data-counts-selected]) {
|
||||||
|
@apply bg-white text-slate-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counts-grid > tbody > tr:nth-child(even) > td:not([data-counts-selected]) {
|
||||||
|
@apply bg-slate-50 text-slate-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .counts-grid > tbody > tr:nth-child(odd) > td:not([data-counts-selected]) {
|
||||||
|
@apply bg-slate-950 text-slate-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .counts-grid > tbody > tr:nth-child(even) > td:not([data-counts-selected]) {
|
||||||
|
@apply bg-slate-900 text-slate-100;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@import "@fontsource-variable/noto-sans-jp";
|
@import "@fontsource-variable/noto-sans-jp";
|
||||||
|
@import './components/counts/countsTable.css';
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
|
|||||||
@@ -78,9 +78,7 @@ const nested: Partial<Record<CountColumn, CountField[]>> = {
|
|||||||
{ key: 'kind', label: 'counts:columns.kind' },
|
{ key: 'kind', label: 'counts:columns.kind' },
|
||||||
{ key: 'name', label: 'history:name' },
|
{ key: 'name', label: 'history:name' },
|
||||||
{ key: 'category', label: 'common:categoryLabel' },
|
{ key: 'category', label: 'common:categoryLabel' },
|
||||||
{ key: 'sections', label: 'counts:columns.sections', children: [
|
{ key: 'sections', label: 'counts:columns.sections' },
|
||||||
{ key: 'beginMs', label: 'counts:columns.begin' },
|
|
||||||
{ key: 'endMs', label: 'counts:columns.end' }] },
|
|
||||||
{ key: 'names', label: 'counts:columns.names', children: nameFields }],
|
{ key: 'names', label: 'counts:columns.names', children: nameFields }],
|
||||||
names: nameFields,
|
names: nameFields,
|
||||||
aliases: [{ key: 'languageCode', label: 'counts:columns.language' }, ...aliasFields],
|
aliases: [{ key: 'languageCode', label: 'counts:columns.language' }, ...aliasFields],
|
||||||
@@ -173,7 +171,7 @@ export const normaliseCountsParams = (
|
|||||||
|
|
||||||
export const countsRequestParams = (params: URLSearchParams): URLSearchParams => {
|
export const countsRequestParams = (params: URLSearchParams): URLSearchParams => {
|
||||||
const result = new URLSearchParams (params)
|
const result = new URLSearchParams (params)
|
||||||
for (const key of ['columns', 'children', 'child_sort'])
|
for (const key of ['columns', 'children', 'child_sort', 'nested_json'])
|
||||||
result.delete (key)
|
result.delete (key)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const countsCellText = (
|
|||||||
case 'resource_id': return String (row.resourceId)
|
case 'resource_id': return String (row.resourceId)
|
||||||
case 'version_no': return String (row.versionNo)
|
case 'version_no': return String (row.versionNo)
|
||||||
case 'is_latest':
|
case 'is_latest':
|
||||||
return translateUIMessage (`boolean.${row.isLatest}`, 'counts', locale).text
|
return row.isLatest ? translateUIMessage ('latestValue', 'counts', locale).text : ''
|
||||||
case 'title': return row.title ?? ''
|
case 'title': return row.title ?? ''
|
||||||
case 'url': return row.url ?? ''
|
case 'url': return row.url ?? ''
|
||||||
case 'thumbnail':
|
case 'thumbnail':
|
||||||
@@ -68,6 +68,20 @@ export const countsCSVCell = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const countsSectionRanges = (value: unknown, locale: LocaleCode): string[] | null => {
|
||||||
|
if (!Array.isArray (value)) return null
|
||||||
|
const time = (ms: number): string => {
|
||||||
|
const parts = msToTime (ms).split (':')
|
||||||
|
if (parts.length === 2) parts[0] = parts[0].padStart (2, '0')
|
||||||
|
return parts.join (':')
|
||||||
|
}
|
||||||
|
return value.map ((section: { beginMs: number; endMs: number | null }) =>
|
||||||
|
translateUIMessage ('sections.range', 'counts', locale, {
|
||||||
|
begin: time (section.beginMs),
|
||||||
|
end: section.endMs == null ? '' : time (section.endMs) }).text)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const displayNames = new Map<string, Intl.DisplayNames> ()
|
const displayNames = new Map<string, Intl.DisplayNames> ()
|
||||||
|
|
||||||
|
|
||||||
@@ -78,6 +92,9 @@ export const countsLogicalName = (
|
|||||||
): { text: string; language?: string } => {
|
): { text: string; language?: string } => {
|
||||||
const value = row[field]
|
const value = row[field]
|
||||||
if (value == null) return { text: '' }
|
if (value == null) return { text: '' }
|
||||||
|
if (field === 'sections')
|
||||||
|
return { text: (countsSectionRanges (value, locale) ?? []).join (
|
||||||
|
translateUIMessage ('sections.separator', 'counts', locale).text) }
|
||||||
const code = String (value)
|
const code = String (value)
|
||||||
if (field === 'languageCode' || field === 'scriptCode')
|
if (field === 'languageCode' || field === 'scriptCode')
|
||||||
{
|
{
|
||||||
@@ -106,7 +123,6 @@ export const countsLogicalName = (
|
|||||||
: `common:categories.${code}`, 'counts', locale)
|
: `common:categories.${code}`, 'counts', locale)
|
||||||
if (typeof value === 'boolean')
|
if (typeof value === 'boolean')
|
||||||
return translateUIMessage (`boolean.${value}`, 'counts', locale)
|
return translateUIMessage (`boolean.${value}`, 'counts', locale)
|
||||||
if (field === 'beginMs' || field === 'endMs') return { text: msToTime (Number (value)) }
|
|
||||||
return { text: String (value) }
|
return { text: String (value) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,15 +146,16 @@ export const countsNestedExport = (
|
|||||||
): unknown => {
|
): unknown => {
|
||||||
const selected = selectedCountChildren (params)
|
const selected = selectedCountChildren (params)
|
||||||
const columns = fields.filter (field => selected.has (`${path}.${field.key}`))
|
const columns = fields.filter (field => selected.has (`${path}.${field.key}`))
|
||||||
if (columns.length === 0)
|
if (columns.length === 0) return rows.map (() => ({ }))
|
||||||
return { [translateUIMessage ('common:countLabel', 'counts', locale).text]: rows.length }
|
|
||||||
return countsNestedOrder (rows, path, params, locale).map (row =>
|
return countsNestedOrder (rows, path, params, locale).map (row =>
|
||||||
Object.fromEntries (columns.map (field => [
|
Object.fromEntries (columns.map (field => [
|
||||||
translateUIMessage (field.label, 'counts', locale).text,
|
translateUIMessage (field.label, 'counts', locale).text,
|
||||||
field.children
|
field.children
|
||||||
? countsNestedExport (row[field.key] as CountRecord[] ?? [], field.children,
|
? countsNestedExport (row[field.key] as CountRecord[] ?? [], field.children,
|
||||||
`${path}.${field.key}`, params, locale)
|
`${path}.${field.key}`, params, locale)
|
||||||
: countsLogicalName (row, field.key, locale).text])))
|
: (field.key === 'sections'
|
||||||
|
? countsSectionRanges (row[field.key], locale)
|
||||||
|
: countsLogicalName (row, field.key, locale).text)])))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -168,10 +185,14 @@ export const countsCellCopy = (
|
|||||||
field: CountField,
|
field: CountField,
|
||||||
params: URLSearchParams,
|
params: URLSearchParams,
|
||||||
locale: LocaleCode,
|
locale: LocaleCode,
|
||||||
): string => field.children
|
): string => {
|
||||||
? countsNestedText (row[field.key as keyof CountsVersion] as CountRecord[] ?? [],
|
if (field.children == null) return countsCellText (row, field.key as CountColumn, locale)
|
||||||
field.children, field.key, params, locale)
|
const rows = row[field.key as keyof CountsVersion] as CountRecord[] ?? []
|
||||||
: countsCellText (row, field.key as CountColumn, locale)
|
return params.get ('nested_json') === '1'
|
||||||
|
? JSON.stringify (
|
||||||
|
countsNestedExport (rows, field.children, field.key, params, locale), null, 2)
|
||||||
|
: countsNestedText (rows, field.children, field.key, params, locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Historical URLs are displayed without fetching current attachments or metadata.
|
// Historical URLs are displayed without fetching current attachments or metadata.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const defaultCountWidths: Record<string, number> = {
|
|||||||
video_ms: 120, original_created_at: 260, original_created_from: 240,
|
video_ms: 120, original_created_at: 260, original_created_from: 240,
|
||||||
original_created_before: 240, name: 240, names: 480, category: 180,
|
original_created_before: 240, name: 240, names: 480, category: 180,
|
||||||
aliases: 360, deprecated: 240, user: 200, created_at: 240, group: 240, count: 160,
|
aliases: 360, deprecated: 240, user: 200, created_at: 240, group: 240, count: 160,
|
||||||
id: 88, versionNo: 88, kind: 140, sections: 240, beginMs: 120, endMs: 120,
|
id: 88, versionNo: 88, kind: 140, sections: 260,
|
||||||
languageCode: 180, scriptCode: 200, autoGenerated: 120 }
|
languageCode: 180, scriptCode: 200, autoGenerated: 120 }
|
||||||
|
|
||||||
type Layout = {
|
type Layout = {
|
||||||
|
|||||||
@@ -35,8 +35,6 @@
|
|||||||
"language": "言語",
|
"language": "言語",
|
||||||
"kind": "内部・外部",
|
"kind": "内部・外部",
|
||||||
"sections": "記載区間",
|
"sections": "記載区間",
|
||||||
"begin": "開始",
|
|
||||||
"end": "終了",
|
|
||||||
"names": "言語別名称",
|
"names": "言語別名称",
|
||||||
"thumbnailDisplay": "サムネール表示"
|
"thumbnailDisplay": "サムネール表示"
|
||||||
},
|
},
|
||||||
@@ -60,7 +58,8 @@
|
|||||||
"nested": {
|
"nested": {
|
||||||
"toggle": "表内表を展開・折り畳み",
|
"toggle": "表内表を展開・折り畳み",
|
||||||
"selection": "親を OFF にしても子項目の選択は保持します。子項目がすべて OFF の表は件数のみ表示します。",
|
"selection": "親を OFF にしても子項目の選択は保持します。子項目がすべて OFF の表は件数のみ表示します。",
|
||||||
"noColumns": "出力項目が選択されていません。"
|
"noColumns": "出力項目が選択されていません。",
|
||||||
|
"asJSON": "内表を JSON 表示"
|
||||||
},
|
},
|
||||||
"sort": {
|
"sort": {
|
||||||
"action": "クリックで単独ソート。Ctrl または長押しでキーを追加・方向変更・解除。NULL は末尾。",
|
"action": "クリックで単独ソート。Ctrl または長押しでキーを追加・方向変更・解除。NULL は末尾。",
|
||||||
@@ -79,5 +78,10 @@
|
|||||||
},
|
},
|
||||||
"resize": {
|
"resize": {
|
||||||
"column": "{{column}}の列幅を変更。境界をドラッグ、タッチでは長押ししてドラッグ。矢印キーでも調整できます。"
|
"column": "{{column}}の列幅を変更。境界をドラッグ、タッチでは長押ししてドラッグ。矢印キーでも調整できます。"
|
||||||
|
},
|
||||||
|
"latestValue": "最新",
|
||||||
|
"sections": {
|
||||||
|
"range": "{{begin}}~{{end}}",
|
||||||
|
"separator": "、"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ const CountsResourcePage = ({ resource }: { resource: CountsResource }) => {
|
|||||||
const [source, setParams] = useSearchParams ()
|
const [source, setParams] = useSearchParams ()
|
||||||
const params = normaliseCountsParams (resource, source)
|
const params = normaliseCountsParams (resource, source)
|
||||||
const canonical = params.toString ()
|
const canonical = params.toString ()
|
||||||
|
const controlParams = new URLSearchParams (params)
|
||||||
|
controlParams.delete ('nested_json')
|
||||||
const request = countsRequestParams (params)
|
const request = countsRequestParams (params)
|
||||||
const [open, setOpen] = useState (false)
|
const [open, setOpen] = useState (false)
|
||||||
const [sidebarOpen, setSidebarOpen] = useState (false)
|
const [sidebarOpen, setSidebarOpen] = useState (false)
|
||||||
@@ -103,8 +105,11 @@ const CountsResourcePage = ({ resource }: { resource: CountsResource }) => {
|
|||||||
<MainArea className="flex min-h-0 min-w-0 flex-col p-2">
|
<MainArea className="flex min-h-0 min-w-0 flex-col p-2">
|
||||||
<PageTitle className="mb-1 text-base">{label (`titles.${resource}`)}</PageTitle>
|
<PageTitle className="mb-1 text-base">{label (`titles.${resource}`)}</PageTitle>
|
||||||
<div className="relative shrink-0 pr-16">
|
<div className="relative shrink-0 pr-16">
|
||||||
<CountsControls key={`${resource}:${canonical}`} resource={resource} applied={params}
|
<CountsControls key={`${resource}:${controlParams}`}
|
||||||
open={open} setOpen={setOpen} execute={next => {
|
resource={resource} applied={params}
|
||||||
|
open={open} setOpen={setOpen}
|
||||||
|
displayJSON={enabled => update ('nested_json', enabled ? '1' : '0')}
|
||||||
|
execute={next => {
|
||||||
if (next.toString () === canonical) void refetch ()
|
if (next.toString () === canonical) void refetch ()
|
||||||
else setParams (next)
|
else setParams (next)
|
||||||
}}/>
|
}}/>
|
||||||
|
|||||||
新しいイシューから参照
ユーザーをブロックする