このコミットが含まれているのは:
+1
-1
@@ -409,7 +409,7 @@ const LocaleRoute: FC = () => {
|
|||||||
void i18n.changeLanguage (activeLocale)
|
void i18n.changeLanguage (activeLocale)
|
||||||
}, [activeLocale, i18n])
|
}, [activeLocale, i18n])
|
||||||
|
|
||||||
const redirect = legacyLocalePath (location)
|
const redirect = legacyLocalePath (location, pathname => resolvePageRoute (pathname) != null)
|
||||||
if (!(isLocaleCode (locale)) && redirect == null)
|
if (!(isLocaleCode (locale)) && redirect == null)
|
||||||
return <NotFound/>
|
return <NotFound/>
|
||||||
if (isError && data == null)
|
if (isError && data == null)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import DateTimeField from '@/components/common/DateTimeField'
|
import DateTimeField from '@/components/common/DateTimeField'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
@@ -9,18 +9,25 @@ import useDialogue from '@/lib/dialogues/useDialogue'
|
|||||||
import { inputClass } from '@/lib/utils'
|
import { inputClass } from '@/lib/utils'
|
||||||
import { useUITranslation } from '@/lib/useUITranslation'
|
import { useUITranslation } from '@/lib/useUITranslation'
|
||||||
|
|
||||||
|
import type { DialogueFormControls } from '@/lib/dialogues/useDialogue'
|
||||||
import type { CountsCondition, CountsDefinition } from '@/lib/countsDefinitions'
|
import type { CountsCondition, CountsDefinition } from '@/lib/countsDefinitions'
|
||||||
|
|
||||||
|
|
||||||
const ConditionEditor = ({ definition, initial, save }: {
|
const ConditionEditor = (
|
||||||
definition: CountsDefinition; initial?: CountsCondition
|
{ definition, initial, controls, save }: {
|
||||||
save: (condition: CountsCondition) => void },
|
definition: CountsDefinition; initial?: CountsCondition
|
||||||
|
controls: DialogueFormControls
|
||||||
|
save: (condition: CountsCondition) => void },
|
||||||
) => {
|
) => {
|
||||||
const { label, message } = useUITranslation ('counts')
|
const { label, message } = useUITranslation ('counts')
|
||||||
const fields = countsFilterFields (definition)
|
const fields = countsFilterFields (definition)
|
||||||
const [field, setField] = useState (initial?.field ?? 'resource_id')
|
const [field, setField] = useState (initial?.field ?? 'resource_id')
|
||||||
const [operator, setOperator] = useState (initial?.operator ?? 'eq')
|
const [operator, setOperator] = useState (initial?.operator ?? 'eq')
|
||||||
const operators = definition.fields.find (item => item.key === field)?.operators ?? []
|
const operators = definition.fields.find (item => item.key === field)?.operators ?? []
|
||||||
|
useEffect (() => {
|
||||||
|
controls.setActions ([{ label: label (initial == null ? 'add' : 'shared:actions.edit'),
|
||||||
|
onSelect: () => save ({ ...initial, field, operator }) }])
|
||||||
|
}, [controls, field, operator, initial, label, save])
|
||||||
return <div className="space-y-2">
|
return <div className="space-y-2">
|
||||||
<select value={field} aria-label={message ('field').text} className={inputClass ()}
|
<select value={field} aria-label={message ('field').text} className={inputClass ()}
|
||||||
onChange={event => { setField (event.target.value); setOperator ('eq') }}>
|
onChange={event => { setField (event.target.value); setOperator ('eq') }}>
|
||||||
@@ -32,8 +39,6 @@ const ConditionEditor = ({ definition, initial, save }: {
|
|||||||
{operators.map (key => <option key={key} value={key}>
|
{operators.map (key => <option key={key} value={key}>
|
||||||
{message (`operators.${key}`).text}</option>)}
|
{message (`operators.${key}`).text}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<Button type="button" onClick={() => save ({ ...initial, field, operator })}>
|
|
||||||
{label (initial == null ? 'add' : 'shared:actions.edit')}</Button>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,14 +53,14 @@ const CountsAdditionalConditions = ({ params, update }: {
|
|||||||
const conditions = readCountsJSON<CountsCondition[]> (params, 'conditions', [])
|
const conditions = readCountsJSON<CountsCondition[]> (params, 'conditions', [])
|
||||||
const write = (next: CountsCondition[]) => update ('conditions', JSON.stringify (next))
|
const write = (next: CountsCondition[]) => update ('conditions', JSON.stringify (next))
|
||||||
const edit = (index?: number) => {
|
const edit = (index?: number) => {
|
||||||
void dialogue.form ({ title: label ('additionalConditions'), body: controls => (
|
const initial = index == null ? undefined : conditions[index]
|
||||||
<ConditionEditor definition={definition}
|
const save = (condition: CountsCondition) => {
|
||||||
initial={index == null ? undefined : conditions[index]}
|
write (index == null ? [...conditions, condition]
|
||||||
save={condition => {
|
: conditions.map ((item, position) => position === index ? condition : item))
|
||||||
write (index == null ? [...conditions, condition]
|
}
|
||||||
: conditions.map ((item, position) => position === index ? condition : item))
|
dialogue.form ({ title: label ('additionalConditions'), footerLayout: 'inline',
|
||||||
controls.close ()
|
body: controls => <ConditionEditor definition={definition}
|
||||||
}}/>) })
|
initial={initial} controls={controls} save={save}/> })
|
||||||
}
|
}
|
||||||
const patch = (index: number, change: Partial<CountsCondition>) => write (
|
const patch = (index: number, change: Partial<CountsCondition>) => write (
|
||||||
conditions.map ((item, position) => position === index ? { ...item, ...change } : item))
|
conditions.map ((item, position) => position === index ? { ...item, ...change } : item))
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import FormField from '@/components/common/FormField'
|
|||||||
import HelpPopover from '@/components/common/HelpPopover'
|
import HelpPopover from '@/components/common/HelpPopover'
|
||||||
import TagInput from '@/components/common/TagInput'
|
import TagInput from '@/components/common/TagInput'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { countsUseLatest, normaliseCountsParams } from '@/lib/counts'
|
import { countsUseLatest, fieldsForCounts, normaliseCountsParams,
|
||||||
|
selectedCountColumns } from '@/lib/counts'
|
||||||
|
import { useCountsDefinition } from '@/lib/countsDefinitions'
|
||||||
import { useClientMotion } from '@/lib/useClientMotion'
|
import { useClientMotion } from '@/lib/useClientMotion'
|
||||||
import { inputClass } from '@/lib/utils'
|
import { inputClass } from '@/lib/utils'
|
||||||
import { useUITranslation } from '@/lib/useUITranslation'
|
import { useUITranslation } from '@/lib/useUITranslation'
|
||||||
@@ -21,21 +23,36 @@ import type { CountsResource } from '@/lib/counts'
|
|||||||
|
|
||||||
const CountsControls = (
|
const CountsControls = (
|
||||||
{ resource, applied, open, setOpen, execute, displayJSON, displayRowNumbers,
|
{ resource, applied, open, setOpen, execute, displayJSON, displayRowNumbers,
|
||||||
reorder, actions }: {
|
displayThumbnail, reorder, actions }: {
|
||||||
resource: CountsResource; applied: URLSearchParams; open: boolean
|
resource: CountsResource; applied: URLSearchParams; open: boolean
|
||||||
setOpen: (open: boolean) => void
|
setOpen: (open: boolean) => void
|
||||||
execute: (params: URLSearchParams) => void
|
execute: (params: URLSearchParams) => void
|
||||||
displayJSON: (enabled: boolean) => void
|
displayJSON: (enabled: boolean) => void
|
||||||
displayRowNumbers: (enabled: boolean) => void
|
displayRowNumbers: (enabled: boolean) => void
|
||||||
|
displayThumbnail: (enabled: boolean) => void
|
||||||
reorder: (value: string) => void; actions?: ReactNode },
|
reorder: (value: string) => void; actions?: ReactNode },
|
||||||
) => {
|
) => {
|
||||||
const { label, message } = useUITranslation ('counts')
|
const { label, message } = useUITranslation ('counts')
|
||||||
|
const definition = useCountsDefinition ()
|
||||||
const id = useId ()
|
const id = useId ()
|
||||||
const motion = useClientMotion ()
|
const motion = useClientMotion ()
|
||||||
const [draft, setDraft] = useState (() => new URLSearchParams (applied))
|
const [draft, setDraft] = useState (() => new URLSearchParams (applied))
|
||||||
const order = applied.get ('order')
|
const order = applied.get ('order')
|
||||||
const nestedJSON = applied.get ('nested_json')
|
const nestedJSON = applied.get ('nested_json')
|
||||||
const rowNumbers = applied.get ('row_numbers')
|
const rowNumbers = applied.get ('row_numbers')
|
||||||
|
const thumbnail = selectedCountColumns (resource, applied, definition).includes ('thumbnail')
|
||||||
|
const details = applied.get ('mode') === 'details'
|
||||||
|
const hasNested = details && fieldsForCounts (resource, definition)
|
||||||
|
.some (field => field.children != null)
|
||||||
|
useEffect (() => {
|
||||||
|
setDraft (previous => {
|
||||||
|
const next = new URLSearchParams (previous)
|
||||||
|
const columns = selectedCountColumns (resource, previous, definition)
|
||||||
|
.filter (column => column !== 'thumbnail')
|
||||||
|
next.set ('columns', (thumbnail ? ['thumbnail', ...columns] : columns).join (','))
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}, [thumbnail, resource, definition])
|
||||||
useEffect (() => {
|
useEffect (() => {
|
||||||
setDraft (previous => {
|
setDraft (previous => {
|
||||||
const next = new URLSearchParams (previous)
|
const next = new URLSearchParams (previous)
|
||||||
@@ -95,14 +112,32 @@ const CountsControls = (
|
|||||||
{['summary', 'details'].map (value => <option key={value} value={value}>
|
{['summary', 'details'].map (value => <option key={value} value={value}>
|
||||||
{message (value).text}</option>)}
|
{message (value).text}</option>)}
|
||||||
</select>
|
</select>
|
||||||
|
{actions}
|
||||||
|
</div>
|
||||||
|
<fieldset className="flex flex-wrap items-center gap-x-3 gap-y-1 border-b py-1
|
||||||
|
dark:border-slate-700">
|
||||||
|
<legend className="sr-only">{label ('displaySettings')}</legend>
|
||||||
<label className="inline-flex items-center gap-1 text-sm">
|
<label className="inline-flex items-center gap-1 text-sm">
|
||||||
<input type="checkbox" checked={draft.get ('row_numbers') === '1'}
|
<input type="checkbox" checked={draft.get ('row_numbers') === '1'}
|
||||||
onChange={event => {
|
onChange={event => {
|
||||||
update ('row_numbers', event.target.checked ? '1' : '0')
|
update ('row_numbers', event.target.checked ? '1' : '0')
|
||||||
displayRowNumbers (event.target.checked)
|
displayRowNumbers (event.target.checked)
|
||||||
}}/>{label ('rowNumbers')}
|
}}/>{label ('rowNumbers')}
|
||||||
</label>{actions}
|
</label>
|
||||||
</div>
|
{details && resource === 'posts' && <label
|
||||||
|
className="inline-flex items-center gap-1 text-sm">
|
||||||
|
<input type="checkbox" checked={thumbnail}
|
||||||
|
onChange={event => displayThumbnail (event.target.checked)}/>
|
||||||
|
{label ('columns.thumbnailDisplay')}
|
||||||
|
</label>}
|
||||||
|
{hasNested && <label className="inline-flex items-center gap-1 text-sm">
|
||||||
|
<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>}
|
||||||
|
</fieldset>
|
||||||
<AnimatedCollapse id={id} open={open}>
|
<AnimatedCollapse id={id} open={open}>
|
||||||
<div className="max-h-[35dvh] overflow-y-auto overscroll-contain
|
<div className="max-h-[35dvh] overflow-y-auto overscroll-contain
|
||||||
border-b py-2 dark:border-slate-700 md:max-h-[40dvh]">
|
border-b py-2 dark:border-slate-700 md:max-h-[40dvh]">
|
||||||
@@ -111,12 +146,6 @@ const CountsControls = (
|
|||||||
{mode === 'summary' ? <CountsAxes params={draft} update={update}/>
|
{mode === 'summary' ? <CountsAxes params={draft} update={update}/>
|
||||||
: <>
|
: <>
|
||||||
<legend className="mb-1 font-semibold">{label ('output')}</legend>
|
<legend className="mb-1 font-semibold">{label ('output')}</legend>
|
||||||
<label className="mb-1 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>
|
|
||||||
<CountsFieldTree resource={resource} params={draft} update={(key, value) => {
|
<CountsFieldTree resource={resource} params={draft} update={(key, value) => {
|
||||||
update (key, value)
|
update (key, value)
|
||||||
if (key === 'order') reorder (value)
|
if (key === 'order') reorder (value)
|
||||||
|
|||||||
@@ -109,9 +109,11 @@ const CountsFieldTree = (
|
|||||||
update ('children', countChildPaths (fields).filter (
|
update ('children', countChildPaths (fields).filter (
|
||||||
key => key.includes ('.') && selected.has (key)).join (','))
|
key => key.includes ('.') && selected.has (key)).join (','))
|
||||||
}
|
}
|
||||||
|
const visibleFields = parent === ''
|
||||||
|
? fields.filter (field => field.key !== 'thumbnail') : fields
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FieldGroup fields={fields} path={parent} params={params} enabled selected={selected}
|
<FieldGroup fields={visibleFields} path={parent} params={params} enabled selected={selected}
|
||||||
select={select} update={update}/>
|
select={select} update={update}/>
|
||||||
</>)
|
</>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,14 +50,19 @@ const CountsResults = (
|
|||||||
switch (column)
|
switch (column)
|
||||||
{
|
{
|
||||||
case 'resource_id':
|
case 'resource_id':
|
||||||
if (resource === 'nico_tags') return <PrefetchLink to="/nico/tags">
|
if (resource === 'nico_tags') return row.name ? (
|
||||||
{row.resourceId}</PrefetchLink>
|
<PrefetchLink to={`/tags/nico?${new URLSearchParams ({ name: row.name })}`}>
|
||||||
|
{row.resourceId}</PrefetchLink>) : row.resourceId
|
||||||
if (resource === 'tag_groups') return <PrefetchLink
|
if (resource === 'tag_groups') return <PrefetchLink
|
||||||
to={`/wiki/${encodeURIComponent (`タググループ:${row.name ?? ''}`)}`}>
|
to={`/wiki/${encodeURIComponent (`タググループ:${row.name ?? ''}`)}`}>
|
||||||
{row.resourceId}</PrefetchLink>
|
{row.resourceId}</PrefetchLink>
|
||||||
return <PrefetchLink to={`/${resource === 'tag_descriptions' ? 'tags' : resource}/${
|
return <PrefetchLink to={`/${resource === 'tag_descriptions' ? 'tags' : resource}/${
|
||||||
encodeURIComponent (row.resourceId)}`}>
|
encodeURIComponent (row.resourceId)}`}>
|
||||||
{row.resourceId}</PrefetchLink>
|
{row.resourceId}</PrefetchLink>
|
||||||
|
case 'name':
|
||||||
|
return resource === 'nico_tags' && row.name ? (
|
||||||
|
<PrefetchLink to={`/tags/nico?${new URLSearchParams ({ name: row.name })}`}>
|
||||||
|
{value}</PrefetchLink>) : value
|
||||||
case 'thumbnail':
|
case 'thumbnail':
|
||||||
{
|
{
|
||||||
const url = countsImageURL (row.thumbnailBase)
|
const url = countsImageURL (row.thumbnailBase)
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ const CountsSortHeader = (
|
|||||||
}}>
|
}}>
|
||||||
<span className="truncate" lang={label.language}>{label.text}</span>
|
<span className="truncate" lang={label.language}>{label.text}</span>
|
||||||
{priority >= 0 && <span aria-hidden>
|
{priority >= 0 && <span aria-hidden>
|
||||||
{direction === 'asc' ? '↑' : '↓'}{sorts.length > 1 && priority + 1}</span>}
|
{direction === 'asc' ? '▲' : '▼'}{sorts.length > 1 && priority + 1}</span>}
|
||||||
</Button>
|
</Button>
|
||||||
<CountsResizeHandle field={field} label={label}/>
|
<CountsResizeHandle field={field} label={label}/>
|
||||||
</th>)
|
</th>)
|
||||||
|
|||||||
@@ -237,7 +237,10 @@ const DialogueProvider: FC<Props> = ({ children }) => {
|
|||||||
</Button>))}
|
</Button>))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex w-full flex-col gap-2 md:w-auto md:flex-row">
|
<div className={cn (
|
||||||
|
'flex w-full gap-2 md:w-auto md:flex-row',
|
||||||
|
active.options.footerLayout === 'inline'
|
||||||
|
? 'flex-row justify-end [&>button]:w-auto' : 'flex-col')}>
|
||||||
<Button
|
<Button
|
||||||
className="w-full md:w-auto"
|
className="w-full md:w-auto"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ export const render = async (
|
|||||||
authentication: Pick<RenderContext, 'cookie' | 'forwardedFor'> = { },
|
authentication: Pick<RenderContext, 'cookie' | 'forwardedFor'> = { },
|
||||||
): Promise<RenderedPage | RouteResponse> => {
|
): Promise<RenderedPage | RouteResponse> => {
|
||||||
const url = new URL (address, origin)
|
const url = new URL (address, origin)
|
||||||
const legacy = legacyLocalePath ({ pathname: url.pathname, search: url.search, hash: '' })
|
const legacy = legacyLocalePath (
|
||||||
|
{ pathname: url.pathname, search: url.search, hash: url.hash },
|
||||||
|
pathname => resolvePageRoute (pathname) != null)
|
||||||
const pathname = legacy == null ? url.pathname : new URL (legacy, origin).pathname
|
const pathname = legacy == null ? url.pathname : new URL (legacy, origin).pathname
|
||||||
const segment = pathname.split ('/')[1]
|
const segment = pathname.split ('/')[1]
|
||||||
const known = resolvePageRoute (pathname)
|
const known = resolvePageRoute (pathname)
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ type DialogueFormOptions = { title: ReactNode
|
|||||||
description?: ReactNode
|
description?: ReactNode
|
||||||
body: (controls: DialogueFormControls) => ReactNode
|
body: (controls: DialogueFormControls) => ReactNode
|
||||||
cancelText?: ReactNode
|
cancelText?: ReactNode
|
||||||
|
footerLayout?: 'inline'
|
||||||
size?: 'default' | 'large' | 'viewport' }
|
size?: 'default' | 'large' | 'viewport' }
|
||||||
|
|
||||||
type DialogueAPI =
|
type DialogueAPI =
|
||||||
|
|||||||
@@ -17,11 +17,6 @@ export const fetchLocales = (): Promise<{ locales: AvailableLocale[] }> =>
|
|||||||
export const LOCALE_LABELS: Record<LocaleCode, string> = {
|
export const LOCALE_LABELS: Record<LocaleCode, string> = {
|
||||||
ja: '日本語', 'ain-Kana': 'ヤウンモシルン アイヌ イタㇰ' }
|
ja: '日本語', 'ain-Kana': 'ヤウンモシルン アイヌ イタㇰ' }
|
||||||
|
|
||||||
const LEGACY_ROOTS = [
|
|
||||||
'posts', 'tags', 'nico', 'theatres', 'materials', 'wiki', 'help',
|
|
||||||
'users', 'settings', 'tos', 'gekanator', 'more']
|
|
||||||
|
|
||||||
|
|
||||||
export const isLocaleCode = (value: unknown): value is LocaleCode =>
|
export const isLocaleCode = (value: unknown): value is LocaleCode =>
|
||||||
SUPPORTED_LOCALES.some (locale => locale === value)
|
SUPPORTED_LOCALES.some (locale => locale === value)
|
||||||
|
|
||||||
@@ -73,11 +68,15 @@ export const switchLocalePath = (
|
|||||||
`/${ locale }${ unlocalisedPath (location.pathname) }${ location.search }${ location.hash }`
|
`/${ locale }${ unlocalisedPath (location.pathname) }${ location.search }${ location.hash }`
|
||||||
|
|
||||||
|
|
||||||
|
// Keep automatic selection independent of the page and API route trees.
|
||||||
|
export const automaticallySelectedLocale = (): LocaleCode => 'ja'
|
||||||
|
|
||||||
|
|
||||||
export const legacyLocalePath = (
|
export const legacyLocalePath = (
|
||||||
location: { pathname: string; search: string; hash: string },
|
location: { pathname: string; search: string; hash: string },
|
||||||
|
isPage: (pathname: string) => boolean = () => false,
|
||||||
): string | null => {
|
): string | null => {
|
||||||
const root = location.pathname.split ('/')[1]
|
if (localeFromPath (location.pathname) != null || !isPage (location.pathname))
|
||||||
return (location.pathname === '/' || LEGACY_ROOTS.includes (root))
|
return null
|
||||||
? `/ja${ location.pathname }${ location.search }${ location.hash }`
|
return switchLocalePath (location, automaticallySelectedLocale ())
|
||||||
: null
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"output": "出力項目",
|
"output": "出力項目",
|
||||||
"filters": "抽出条件",
|
"filters": "抽出条件",
|
||||||
"mode": "表示",
|
"mode": "表示",
|
||||||
|
"displaySettings": "表示設定",
|
||||||
"summary": "集計",
|
"summary": "集計",
|
||||||
"details": "明細",
|
"details": "明細",
|
||||||
"axis": "集計軸",
|
"axis": "集計軸",
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
"kind": "内部・外部",
|
"kind": "内部・外部",
|
||||||
"sections": "記載区間",
|
"sections": "記載区間",
|
||||||
"names": "言語別名称",
|
"names": "言語別名称",
|
||||||
"thumbnailDisplay": "サムネール表示"
|
"thumbnailDisplay": "$t(shared:labels.thumbnail)"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"from": "開始(端末の時刻帯・以上)",
|
"from": "開始(端末の時刻帯・以上)",
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ const CountsResourceContent = ({ resource }: { resource: CountsResource }) => {
|
|||||||
controlParams.delete ('nested_json')
|
controlParams.delete ('nested_json')
|
||||||
controlParams.delete ('order')
|
controlParams.delete ('order')
|
||||||
controlParams.delete ('row_numbers')
|
controlParams.delete ('row_numbers')
|
||||||
|
controlParams.set ('columns', selectedCountColumns (resource, params, definition)
|
||||||
|
.filter (column => column !== 'thumbnail').join (','))
|
||||||
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)
|
||||||
@@ -195,6 +197,11 @@ const CountsResourceContent = ({ resource }: { resource: CountsResource }) => {
|
|||||||
reorder={value => update ('order', value)}
|
reorder={value => update ('order', value)}
|
||||||
displayJSON={enabled => update ('nested_json', enabled ? '1' : '0')}
|
displayJSON={enabled => update ('nested_json', enabled ? '1' : '0')}
|
||||||
displayRowNumbers={enabled => update ('row_numbers', enabled ? '1' : '0')}
|
displayRowNumbers={enabled => update ('row_numbers', enabled ? '1' : '0')}
|
||||||
|
displayThumbnail={enabled => {
|
||||||
|
const columns = selectedCountColumns (resource, params, definition)
|
||||||
|
.filter (column => column !== 'thumbnail')
|
||||||
|
update ('columns', (enabled ? ['thumbnail', ...columns] : columns).join (','))
|
||||||
|
}}
|
||||||
execute={next => {
|
execute={next => {
|
||||||
if (next.toString () === canonical) void refetch ()
|
if (next.toString () === canonical) void refetch ()
|
||||||
else setParams (next)
|
else setParams (next)
|
||||||
|
|||||||
新しいイシューから参照
ユーザーをブロックする