このコミットが含まれているのは:
2026-10-27 01:11:13 +09:00
コミット 30b5fe5f05
17個のファイルの変更、766行の追加、168行の削除
+39 -26
ファイルの表示
@@ -22,7 +22,9 @@ const ConditionEditor = (
save: (condition: CountsCondition) => void },
) => {
const { label, message } = useUITranslation ('counts')
const fields = countsFilterFields (definition)
const fields = definition.filterFields ?? definition.fields
const fieldName = (key: string) => (fields.find (item => item.key === key)?.labels
?? [countsFieldLabel (key)]).map (key => message (key).text).join (' › ')
const [condition, setCondition] = useState<CountsCondition> (() => {
const next = { ...initial, field: initial?.field ?? 'resource_id',
operator: initial?.operator ?? 'eq' }
@@ -30,7 +32,7 @@ const ConditionEditor = (
return next
})
const { field, operator } = condition
const operators = definition.fields.find (item => item.key === field)?.operators ?? []
const operators = fields.find (item => item.key === field)?.operators ?? []
useEffect (() => {
controls.setActions ([{ label: label (initial == null ? 'add' : 'shared:actions.edit'),
onSelect: () => save (condition) }])
@@ -39,11 +41,11 @@ const ConditionEditor = (
<select value={field} aria-label={message ('field').text} className={inputClass ()}
onChange={event => {
const field = event.target.value
const operator = definition.fields.find (item => item.key === field)?.operators[0]
const operator = fields.find (item => item.key === field)?.operators[0]
if (operator != null) setCondition ({ field, operator })
}}>
{Object.keys (fields).filter (key => fields[key] !== 'json').map (key => (
<option key={key} value={key}>{message (countsFieldLabel (key)).text}</option>))}
{fields.filter (field => field.operators.length > 0).map (field => (
<option key={field.key} value={field.key}>{fieldName (field.key)}</option>))}
</select>
<select value={operator} aria-label={message ('operator').text} className={inputClass ()}
onChange={event => setCondition (previous => {
@@ -68,6 +70,9 @@ const CountsAdditionalConditions = ({ params, update }: {
const definition = useCountsDefinition ()
const dialogue = useDialogue ()
const conditions = readCountState<CountsCondition[]> (params, 'conditions', [])
const metadata = definition.filterFields ?? definition.fields
const fieldName = (key: string) => (metadata.find (item => item.key === key)?.labels
?? [countsFieldLabel (key)]).map (key => message (key).text).join (' › ')
const write = (next: CountsCondition[]) => update ('conditions', encodeCountState ('conditions', next))
const edit = (index?: number) => {
const initial = index == null ? undefined : conditions[index]
@@ -81,37 +86,45 @@ const CountsAdditionalConditions = ({ params, update }: {
}
const patch = (index: number, change: Partial<CountsCondition>) => write (
conditions.map ((item, position) => position === index ? { ...item, ...change } : item))
const valueInput = (condition: CountsCondition, index: number, key: 'value' | 'end') => {
const type = countsFilterFields (definition)[condition.field]
const aria = message (key === 'end' ? 'through' : 'value')
if (type === 'boolean' || condition.field === 'event_type')
{
const options: string[] = type === 'boolean' ? ['true', 'false'] : [...countsEventTypes]
const namespace = type === 'boolean' ? 'truthValues' : 'events'
return <select key={key} value={condition[key] ?? ''} disabled={condition.blank}
className={inputClass (false, 'w-36 py-1')} aria-label={aria.text}
onChange={event => patch (index, { [key]: event.target.value })}>
<option value=""/>
{options.map (value => <option key={value} value={value}
lang={message (`${namespace}.${value}`).language}>
{message (`${namespace}.${value}`).text}</option>)}
</select>
}
if (type === 'datetime')
return <DateTimeField precision="second" key={key}
value={condition[key] ?? ''} disabled={condition.blank}
aria-label={aria.text} lang={aria.language}
onChange={value => patch (index, { [key]: value ?? '' })}/>
return <input key={key} value={condition[key] ?? ''} disabled={condition.blank}
type={(type === 'integer' || type === 'interval') ? 'number' : 'text'}
className={inputClass (false, 'w-36 py-1')}
aria-label={aria.text} lang={aria.language}
onChange={event => patch (index, { [key]: event.target.value })}/>
}
return <fieldset className="space-y-1">
<legend className="flex items-center gap-2">{label ('additionalConditions')}
<Button type="button" variant="outline" size="sm" onClick={() => edit ()}>
{label ('add')}</Button></legend>
{conditions.map ((condition, index) => {
const datetime = countsFilterFields (definition)[condition.field] === 'datetime'
return <div key={index} className="flex flex-wrap items-center gap-1">
<button type="button" onClick={() => edit (index)}>
{label (countsFieldLabel (condition.field))}
{fieldName (condition.field)}
{' · '}{label (`operators.${condition.operator}`)}
</button>
{(condition.operator === 'range' ? ['value', 'end'] as const : ['value'] as const)
.map (key => condition.field === 'event_type' ? (
<select key={key} value={condition[key] ?? ''} disabled={condition.blank}
className={inputClass (false, 'w-36 py-1')}
aria-label={message ('value').text}
onChange={event => patch (index, { [key]: event.target.value })}>
<option value=""/>
{condition[key] && !countsEventTypes.some (event => event === condition[key])
&& <option value={condition[key]}>{condition[key]}</option>}
{countsEventTypes.map (event => <option key={event} value={event}
lang={message (`events.${event}`).language}>
{message (`events.${event}`).text}</option>)}
</select>) : (datetime ? <DateTimeField precision="second" key={key}
value={condition[key] ?? ''} disabled={condition.blank}
aria-label={message (key === 'end' ? 'through' : 'value').text}
onChange={value => patch (index, { [key]: value ?? '' })}/> : (
<input key={key} value={condition[key] ?? ''} disabled={condition.blank}
className={inputClass (false, 'w-36 py-1')}
aria-label={message ('value').text}
onChange={event => patch (index, { [key]: event.target.value })}/>)))}
.map (key => valueInput (condition, index, key))}
<label className="inline-flex items-center gap-1">
<input type="checkbox" checked={condition.blank ?? false}
onChange={event => patch (index, { blank: event.target.checked })}/>
+5 -3
ファイルの表示
@@ -26,8 +26,9 @@ import type { CountsResource } from '@/lib/counts'
const CountsControls = (
{ resource, applied, open, setOpen, execute, displayJSON, displayRowNumbers,
displayThumbnail, reorder, actions }: {
displayThumbnail, reorder, actions, pending = false }: {
resource: CountsResource; applied: URLSearchParams; open: boolean
pending?: boolean
setOpen: (open: boolean) => void
execute: (params: URLSearchParams) => void
displayJSON: (enabled: boolean) => void
@@ -106,9 +107,9 @@ const CountsControls = (
})
return <form className="shrink-0" onSubmit={event => {
event.preventDefault ()
if (pending) return
const next = new URLSearchParams (draft)
next.delete ('page')
next.delete ('execution_at')
execute (normaliseCountsParams (resource, next, definition))
}}>
{!history && <div className="flex flex-wrap items-center gap-2 border-b pb-1
@@ -130,7 +131,8 @@ const CountsControls = (
</div>
</div>}
<div className="flex flex-wrap items-center gap-2 border-b py-1 dark:border-slate-700">
<Button type="submit" size="sm">{label ('run')}</Button>
<Button type="submit" size="sm" aria-disabled={pending}
className="aria-disabled:opacity-50">{label ('run')}</Button>
<Button type="button" size="sm" variant="outline" aria-expanded={open}
aria-controls={id} onClick={() => setOpen (!open)}>
{label ('conditions')}<span aria-hidden className="inline-block" style={{
+4 -3
ファイルの表示
@@ -8,10 +8,11 @@ import type { FC, ReactNode } from 'react'
type Props = {
children: ReactNode
className?: string }
className?: string
animateLayout?: boolean }
const MainArea: FC<Props> = ({ children, className }) => {
const MainArea: FC<Props> = ({ children, className, animateLayout = true }) => {
const behaviourSettings = useClientBehaviourSettings ()
const animationMode = behaviourSettings.animation ?? 'normal'
const layoutTransition = clientAnimationTransition (
@@ -23,7 +24,7 @@ const MainArea: FC<Props> = ({ children, className }) => {
<motion.main
transition={{ layout: layoutTransition }}
className={cn ('flex-1 overflow-y-auto p-4', className)}
layout={animationMode === 'off' ? false : 'position'}>
layout={(animationMode === 'off' || !animateLayout) ? false : 'position'}>
{children}
</motion.main>)
}
+3 -1
ファイルの表示
@@ -16,6 +16,7 @@ type Props = {
className?: string
maxWidth?: number
maxWidthRatio?: number
animateLayout?: boolean
onWidthChange?: (width: number) => void
sidebarKey: string
side?: SidebarSide }
@@ -69,6 +70,7 @@ const SidebarComponent: FC<Props> = ({
className,
maxWidth,
maxWidthRatio,
animateLayout = true,
onWidthChange,
sidebarKey,
side = 'left',
@@ -316,7 +318,7 @@ const SidebarComponent: FC<Props> = ({
return (
<motion.div
ref={rootRef}
layout={animationMode === 'off' ? false : 'position'}
layout={(animationMode === 'off' || !animateLayout) ? false : 'position'}
transition={{ layout: layoutTransition }}
style={style}
className={cn (